datadog 2.18.0 → 2.20.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 (297) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +51 -10
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +58 -49
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -1
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -6
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -1
  8. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +37 -26
  9. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +0 -1
  10. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -1
  11. data/ext/libdatadog_api/extconf.rb +3 -1
  12. data/ext/libdatadog_extconf_helpers.rb +13 -3
  13. data/lib/datadog/appsec/api_security/route_extractor.rb +7 -1
  14. data/lib/datadog/appsec/component.rb +3 -13
  15. data/lib/datadog/appsec/context.rb +23 -0
  16. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +2 -1
  17. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +2 -1
  18. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +0 -1
  19. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +0 -1
  20. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +14 -22
  21. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +23 -2
  22. data/lib/datadog/appsec/contrib/rails/patcher.rb +14 -26
  23. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +27 -0
  24. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
  25. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +0 -1
  26. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +23 -0
  27. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +8 -18
  28. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
  29. data/lib/datadog/appsec/event.rb +3 -18
  30. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +17 -1
  31. data/lib/datadog/appsec/metrics/collector.rb +7 -3
  32. data/lib/datadog/appsec/metrics/telemetry.rb +1 -1
  33. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +28 -0
  34. data/lib/datadog/appsec/metrics.rb +1 -0
  35. data/lib/datadog/appsec/security_engine/engine.rb +14 -32
  36. data/lib/datadog/appsec/security_engine/result.rb +16 -0
  37. data/lib/datadog/appsec/security_engine/runner.rb +18 -4
  38. data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
  39. data/lib/datadog/appsec/trace_keeper.rb +24 -0
  40. data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
  41. data/lib/datadog/appsec.rb +0 -7
  42. data/lib/datadog/auto_instrument_base.rb +2 -1
  43. data/lib/datadog/core/configuration/option.rb +29 -20
  44. data/lib/datadog/core/configuration/option_definition.rb +2 -2
  45. data/lib/datadog/core/configuration/options.rb +13 -7
  46. data/lib/datadog/core/configuration/settings.rb +20 -0
  47. data/lib/datadog/core/telemetry/component.rb +8 -4
  48. data/lib/datadog/core/telemetry/event/app_started.rb +21 -3
  49. data/lib/datadog/di/boot.rb +7 -0
  50. data/lib/datadog/di/component.rb +7 -0
  51. data/lib/datadog/di/instrumenter.rb +11 -18
  52. data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
  53. data/lib/datadog/di/probe_file_loader.rb +82 -0
  54. data/lib/datadog/di/probe_notification_builder.rb +21 -16
  55. data/lib/datadog/di/remote.rb +3 -5
  56. data/lib/datadog/di/serializer.rb +6 -2
  57. data/lib/datadog/di.rb +0 -7
  58. data/lib/datadog/kit/appsec/events/v2.rb +196 -0
  59. data/lib/datadog/kit/appsec/events.rb +11 -10
  60. data/lib/datadog/kit/identity.rb +17 -11
  61. data/lib/datadog/opentelemetry/api/baggage.rb +2 -2
  62. data/lib/datadog/opentelemetry/api/context.rb +10 -9
  63. data/lib/datadog/opentelemetry/sdk/propagator.rb +4 -4
  64. data/lib/datadog/opentelemetry/sdk/span_processor.rb +8 -8
  65. data/lib/datadog/opentelemetry/sdk/trace/span.rb +14 -10
  66. data/lib/datadog/opentelemetry/trace.rb +4 -4
  67. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -0
  68. data/lib/datadog/profiling/collectors/info.rb +41 -0
  69. data/lib/datadog/profiling/component.rb +1 -0
  70. data/lib/datadog/profiling/exporter.rb +9 -3
  71. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  72. data/lib/datadog/profiling/tag_builder.rb +2 -0
  73. data/lib/datadog/profiling.rb +7 -8
  74. data/lib/datadog/single_step_instrument.rb +9 -0
  75. data/lib/datadog/tracing/analytics.rb +1 -1
  76. data/lib/datadog/tracing/buffer.rb +7 -7
  77. data/lib/datadog/tracing/configuration/dynamic.rb +4 -6
  78. data/lib/datadog/tracing/configuration/ext.rb +3 -2
  79. data/lib/datadog/tracing/configuration/settings.rb +17 -0
  80. data/lib/datadog/tracing/context.rb +2 -2
  81. data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
  82. data/lib/datadog/tracing/contrib/action_cable/integration.rb +1 -1
  83. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +1 -1
  84. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  85. data/lib/datadog/tracing/contrib/action_pack/integration.rb +1 -1
  86. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  87. data/lib/datadog/tracing/contrib/active_job/event.rb +8 -8
  88. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  89. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  90. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  91. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  92. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  93. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  94. data/lib/datadog/tracing/contrib/active_job/integration.rb +1 -1
  95. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  96. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +3 -3
  97. data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +1 -2
  98. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
  99. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  100. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +5 -5
  101. data/lib/datadog/tracing/contrib/active_record/integration.rb +1 -1
  102. data/lib/datadog/tracing/contrib/active_record/utils.rb +15 -15
  103. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +13 -7
  104. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
  105. data/lib/datadog/tracing/contrib/active_support/integration.rb +1 -1
  106. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +2 -1
  107. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +7 -9
  108. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  109. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +2 -2
  110. data/lib/datadog/tracing/contrib/aws/parsed_context.rb +3 -1
  111. data/lib/datadog/tracing/contrib/aws/patcher.rb +5 -1
  112. data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
  113. data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +1 -1
  114. data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +1 -1
  115. data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +1 -1
  116. data/lib/datadog/tracing/contrib/aws/service/s3.rb +1 -1
  117. data/lib/datadog/tracing/contrib/aws/service/sns.rb +1 -1
  118. data/lib/datadog/tracing/contrib/aws/service/sqs.rb +1 -1
  119. data/lib/datadog/tracing/contrib/aws/service/states.rb +1 -1
  120. data/lib/datadog/tracing/contrib/aws/services.rb +7 -7
  121. data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +1 -1
  122. data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +1 -1
  123. data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +1 -1
  124. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +1 -1
  125. data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +1 -1
  126. data/lib/datadog/tracing/contrib/configurable.rb +6 -6
  127. data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +4 -4
  128. data/lib/datadog/tracing/contrib/dalli/ext.rb +3 -2
  129. data/lib/datadog/tracing/contrib/dalli/integration.rb +1 -1
  130. data/lib/datadog/tracing/contrib/delayed_job/integration.rb +1 -1
  131. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +3 -2
  132. data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +4 -4
  133. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +51 -53
  134. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +5 -5
  135. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +2 -2
  136. data/lib/datadog/tracing/contrib/ethon/ext.rb +3 -2
  137. data/lib/datadog/tracing/contrib/ethon/integration.rb +1 -1
  138. data/lib/datadog/tracing/contrib/excon/ext.rb +3 -2
  139. data/lib/datadog/tracing/contrib/excon/integration.rb +1 -1
  140. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  141. data/lib/datadog/tracing/contrib/ext.rb +3 -3
  142. data/lib/datadog/tracing/contrib/extensions.rb +9 -9
  143. data/lib/datadog/tracing/contrib/faraday/ext.rb +3 -2
  144. data/lib/datadog/tracing/contrib/faraday/integration.rb +1 -1
  145. data/lib/datadog/tracing/contrib/faraday/middleware.rb +4 -2
  146. data/lib/datadog/tracing/contrib/grape/endpoint.rb +8 -8
  147. data/lib/datadog/tracing/contrib/grape/integration.rb +1 -1
  148. data/lib/datadog/tracing/contrib/graphql/integration.rb +1 -1
  149. data/lib/datadog/tracing/contrib/graphql/patcher.rb +2 -2
  150. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +24 -24
  151. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +8 -8
  152. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +3 -3
  153. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +1 -1
  154. data/lib/datadog/tracing/contrib/grpc/integration.rb +1 -1
  155. data/lib/datadog/tracing/contrib/hanami/ext.rb +2 -2
  156. data/lib/datadog/tracing/contrib/hanami/integration.rb +1 -1
  157. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +1 -1
  158. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +9 -11
  159. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +4 -4
  160. data/lib/datadog/tracing/contrib/http/ext.rb +3 -2
  161. data/lib/datadog/tracing/contrib/http/instrumentation.rb +5 -5
  162. data/lib/datadog/tracing/contrib/httpclient/ext.rb +3 -2
  163. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +3 -3
  164. data/lib/datadog/tracing/contrib/httpclient/integration.rb +1 -1
  165. data/lib/datadog/tracing/contrib/httprb/ext.rb +3 -2
  166. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +4 -4
  167. data/lib/datadog/tracing/contrib/httprb/integration.rb +1 -1
  168. data/lib/datadog/tracing/contrib/kafka/event.rb +1 -1
  169. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +1 -1
  170. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +1 -1
  171. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +1 -1
  172. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +1 -1
  173. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +1 -1
  174. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +1 -1
  175. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +1 -1
  176. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +1 -1
  177. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +1 -1
  178. data/lib/datadog/tracing/contrib/kafka/integration.rb +1 -1
  179. data/lib/datadog/tracing/contrib/karafka/monitor.rb +13 -13
  180. data/lib/datadog/tracing/contrib/karafka/patcher.rb +4 -4
  181. data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
  182. data/lib/datadog/tracing/contrib/lograge/integration.rb +1 -1
  183. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +1 -1
  184. data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -1
  185. data/lib/datadog/tracing/contrib/mongodb/integration.rb +1 -1
  186. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
  187. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +6 -6
  188. data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -1
  189. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -6
  190. data/lib/datadog/tracing/contrib/mysql2/integration.rb +1 -1
  191. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +1 -1
  192. data/lib/datadog/tracing/contrib/opensearch/ext.rb +3 -2
  193. data/lib/datadog/tracing/contrib/opensearch/integration.rb +1 -2
  194. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +68 -70
  195. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +5 -5
  196. data/lib/datadog/tracing/contrib/patcher.rb +7 -9
  197. data/lib/datadog/tracing/contrib/pg/integration.rb +1 -1
  198. data/lib/datadog/tracing/contrib/presto/ext.rb +1 -1
  199. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +3 -3
  200. data/lib/datadog/tracing/contrib/presto/integration.rb +1 -1
  201. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +1 -1
  202. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +1 -1
  203. data/lib/datadog/tracing/contrib/que/integration.rb +1 -1
  204. data/lib/datadog/tracing/contrib/racecar/event.rb +1 -1
  205. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +2 -2
  206. data/lib/datadog/tracing/contrib/racecar/events/consume.rb +1 -1
  207. data/lib/datadog/tracing/contrib/racecar/events/message.rb +2 -2
  208. data/lib/datadog/tracing/contrib/racecar/integration.rb +1 -1
  209. data/lib/datadog/tracing/contrib/rack/header_collection.rb +1 -1
  210. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +32 -32
  211. data/lib/datadog/tracing/contrib/rack/integration.rb +1 -1
  212. data/lib/datadog/tracing/contrib/rack/middlewares.rb +21 -17
  213. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  214. data/lib/datadog/tracing/contrib/rack/request_queue.rb +2 -2
  215. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +1 -1
  216. data/lib/datadog/tracing/contrib/rails/integration.rb +1 -1
  217. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  218. data/lib/datadog/tracing/contrib/rails/middlewares.rb +1 -1
  219. data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -1
  220. data/lib/datadog/tracing/contrib/rails/runner.rb +62 -40
  221. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +4 -4
  222. data/lib/datadog/tracing/contrib/rake/integration.rb +1 -1
  223. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +2 -2
  224. data/lib/datadog/tracing/contrib/redis/ext.rb +3 -2
  225. data/lib/datadog/tracing/contrib/redis/integration.rb +2 -2
  226. data/lib/datadog/tracing/contrib/redis/patcher.rb +4 -4
  227. data/lib/datadog/tracing/contrib/redis/quantize.rb +1 -1
  228. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  229. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +4 -4
  230. data/lib/datadog/tracing/contrib/registry.rb +1 -1
  231. data/lib/datadog/tracing/contrib/resque/integration.rb +1 -1
  232. data/lib/datadog/tracing/contrib/resque/resque_job.rb +1 -1
  233. data/lib/datadog/tracing/contrib/rest_client/ext.rb +3 -2
  234. data/lib/datadog/tracing/contrib/rest_client/integration.rb +1 -1
  235. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -3
  236. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +1 -1
  237. data/lib/datadog/tracing/contrib/roda/integration.rb +1 -1
  238. data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
  239. data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +1 -1
  240. data/lib/datadog/tracing/contrib/sequel/database.rb +5 -5
  241. data/lib/datadog/tracing/contrib/sequel/dataset.rb +1 -1
  242. data/lib/datadog/tracing/contrib/sequel/integration.rb +1 -1
  243. data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -1
  244. data/lib/datadog/tracing/contrib/shoryuken/integration.rb +1 -1
  245. data/lib/datadog/tracing/contrib/sidekiq/integration.rb +1 -1
  246. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
  247. data/lib/datadog/tracing/contrib/sinatra/integration.rb +1 -1
  248. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +38 -40
  249. data/lib/datadog/tracing/contrib/sneakers/integration.rb +1 -1
  250. data/lib/datadog/tracing/contrib/stripe/integration.rb +1 -1
  251. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  252. data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +1 -1
  253. data/lib/datadog/tracing/contrib/trilogy/ext.rb +1 -1
  254. data/lib/datadog/tracing/contrib/trilogy/integration.rb +1 -1
  255. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +11 -11
  256. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -6
  257. data/lib/datadog/tracing/diagnostics/environment_logger.rb +8 -2
  258. data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
  259. data/lib/datadog/tracing/distributed/baggage.rb +73 -8
  260. data/lib/datadog/tracing/distributed/datadog.rb +4 -5
  261. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +11 -13
  262. data/lib/datadog/tracing/distributed/helpers.rb +1 -1
  263. data/lib/datadog/tracing/distributed/none.rb +4 -2
  264. data/lib/datadog/tracing/distributed/propagation.rb +4 -1
  265. data/lib/datadog/tracing/distributed/propagation_policy.rb +1 -1
  266. data/lib/datadog/tracing/distributed/trace_context.rb +22 -16
  267. data/lib/datadog/tracing/event.rb +5 -7
  268. data/lib/datadog/tracing/flush.rb +1 -1
  269. data/lib/datadog/tracing/metadata/analytics.rb +1 -1
  270. data/lib/datadog/tracing/metadata/tagging.rb +4 -4
  271. data/lib/datadog/tracing/pipeline/span_filter.rb +3 -1
  272. data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
  273. data/lib/datadog/tracing/pipeline.rb +1 -1
  274. data/lib/datadog/tracing/sampling/ext.rb +0 -2
  275. data/lib/datadog/tracing/sampling/rule_sampler.rb +30 -30
  276. data/lib/datadog/tracing/sampling/span/rule_parser.rb +1 -1
  277. data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
  278. data/lib/datadog/tracing/span.rb +1 -1
  279. data/lib/datadog/tracing/span_event.rb +10 -10
  280. data/lib/datadog/tracing/span_link.rb +12 -12
  281. data/lib/datadog/tracing/span_operation.rb +31 -11
  282. data/lib/datadog/tracing/trace_digest.rb +21 -23
  283. data/lib/datadog/tracing/trace_operation.rb +84 -88
  284. data/lib/datadog/tracing/trace_segment.rb +2 -2
  285. data/lib/datadog/tracing/tracer.rb +36 -38
  286. data/lib/datadog/tracing/transport/http/client.rb +1 -1
  287. data/lib/datadog/tracing/transport/http/traces.rb +2 -2
  288. data/lib/datadog/tracing/transport/io/client.rb +5 -5
  289. data/lib/datadog/tracing/transport/io/traces.rb +4 -4
  290. data/lib/datadog/tracing/transport/statistics.rb +1 -1
  291. data/lib/datadog/tracing/transport/traces.rb +5 -5
  292. data/lib/datadog/tracing/workers/trace_writer.rb +12 -12
  293. data/lib/datadog/tracing/workers.rb +2 -2
  294. data/lib/datadog/tracing.rb +2 -2
  295. data/lib/datadog/version.rb +1 -1
  296. data/lib/datadog.rb +7 -0
  297. metadata +17 -6
@@ -212,21 +212,6 @@ uint64_t native_thread_id_for(VALUE thread) {
212
212
  #endif
213
213
  }
214
214
 
215
- // Returns the stack depth by using the same approach as rb_profile_frames and backtrace_each: get the positions
216
- // of the end and current frame pointers and subtracting them.
217
- ptrdiff_t stack_depth_for(VALUE thread) {
218
- const rb_execution_context_t *ec = thread_struct_from_object(thread)->ec;
219
- const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
220
-
221
- if (end_cfp == NULL) return 0;
222
-
223
- // Skip dummy frame, as seen in `backtrace_each` (`vm_backtrace.c`) and our custom rb_profile_frames
224
- // ( https://github.com/ruby/ruby/blob/4bd38e8120f2fdfdd47a34211720e048502377f1/vm_backtrace.c#L890-L914 )
225
- end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
226
-
227
- return end_cfp <= cfp ? 0 : end_cfp - cfp - 1;
228
- }
229
-
230
215
  // This was renamed in Ruby 3.2
231
216
  #if !defined(ccan_list_for_each) && defined(list_for_each)
232
217
  #define ccan_list_for_each list_for_each
@@ -360,11 +345,16 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
360
345
  }
361
346
  #endif
362
347
 
363
- // In PROF-11475 we spotted a crash when calling `rb_iseq_line_no` from this method. We couldn't reproduce or
364
- // figure out the root cause, but "just in case", we're validating that the iseq looks valid and that the
365
- // `n` used for the position is also sane, and if they don't look good, we don't calculate the line, rather
366
- // than potentially trigger any issues.
367
- if (RB_UNLIKELY(!RB_TYPE_P((VALUE) iseq, T_IMEMO) || n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) return 0;
348
+ // In PROF-11475 we spotted a crash when calling `rb_iseq_line_no` from this method.
349
+ // We were only able to reproduce this issue on Ruby 2.6 and 2.7, not 2.5 or the 3.x series (tried 3.0, 3.2 and 3.4).
350
+ // Note that going out of bounds doesn't crash every time, as usual with C we may just read garbage or get lucky.
351
+ //
352
+ // For those problematic Rubies, we observed that when we try to take a sample in the middle of processing the
353
+ // VM `LEAVE` instruction, the value of `n` can violate the documented assumptions above and be
354
+ // `n > ISEQ_BODY(iseq)->iseq_size)`.
355
+ //
356
+ // To work around this and any other potential issues, we validate here that the bytecode position is sane.
357
+ if (RB_UNLIKELY(n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) return 0;
368
358
 
369
359
  if (lineno) *lineno = rb_iseq_line_no(iseq, pos);
370
360
  #ifdef USE_ISEQ_NODE_ID
@@ -410,6 +400,9 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
410
400
  // * Add frame_flags.same_frame and logic to skip redoing work if the buffer already contains the same data we're collecting
411
401
  // * Skipped use of rb_callable_method_entry_t (cme) for Ruby frames as it doesn't impact us.
412
402
  // * Imported fix from https://github.com/ruby/ruby/pull/8280 to keep us closer to upstream
403
+ // * Added potential fix for https://github.com/ruby/ruby/pull/13643 (this one is a just-in-case, unclear if it happens
404
+ // for ddtrace)
405
+ // * Reversed order of iteration to better enable caching
413
406
  //
414
407
  // What is rb_profile_frames?
415
408
  // `rb_profile_frames` is a Ruby VM debug API added for use by profilers for sampling the stack trace of a Ruby thread.
@@ -445,6 +438,16 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
445
438
  // support sampling any thread (including the current) passed as an argument
446
439
  rb_thread_t *th = thread_struct_from_object(thread);
447
440
  const rb_execution_context_t *ec = th->ec;
441
+
442
+ // As of this writing, we don't support profiling with MN enabled, and this only happens in that mode, but as we
443
+ // probably want to experiment with it in the future, I've decided to import https://github.com/ruby/ruby/pull/9310
444
+ // here.
445
+ if (ec == NULL) return 0;
446
+
447
+ // I suspect this won't happen for ddtrace, but just-in-case we've imported a potential fix for
448
+ // https://github.com/ruby/ruby/pull/13643 by assuming that these can be NULL/zero with the cfp being non-NULL yet.
449
+ if (ec->vm_stack == NULL || ec->vm_stack_size == 0) return 0;
450
+
448
451
  const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
449
452
  #ifndef NO_JIT_RETURN
450
453
  const rb_control_frame_t *top = cfp;
@@ -462,11 +465,6 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
462
465
  // it from https://github.com/ruby/ruby/pull/7116 in a "just in case" kind of mindset.
463
466
  if (cfp == NULL) return 0;
464
467
 
465
- // As of this writing, we don't support profiling with MN enabled, and this only happens in that mode, but as we
466
- // probably want to experiment with it in the future, I've decided to import https://github.com/ruby/ruby/pull/9310
467
- // here.
468
- if (ec == NULL) return 0;
469
-
470
468
  // Fix: Skip dummy frame that shows up in main thread.
471
469
  //
472
470
  // According to a comment in `backtrace_each` (`vm_backtrace.c`), there's two dummy frames that we should ignore
@@ -483,7 +481,20 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
483
481
  // See comment on `record_placeholder_stack_in_native_code` for a full explanation of what this means (and why we don't just return 0)
484
482
  if (end_cfp <= cfp) return PLACEHOLDER_STACK_IN_NATIVE_CODE;
485
483
 
486
- for (i=0; i<limit && cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) {
484
+ // This is the position just after the top of the stack -- e.g. where a new frame pushed on the stack would end up.
485
+ const rb_control_frame_t *top_sentinel = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
486
+
487
+ // We iterate the stack from bottom (beginning of thread) to the top (currently-active frame). This is different
488
+ // from upstream rb_profile_frames, but actually matches what `backtrace_each` does (yes, different Ruby VM APIs
489
+ // iterate in different directions).
490
+ // We do this to better take advantage of the `same_frame` caching mechanism: By starting from the bottom of the
491
+ // stack towards the top, we can usually keep most of the stack intact when the code is only going up and down
492
+ // a few methods at the top. Before this change, the cache was really only useful if between samples the app had
493
+ // not moved from the current stack, as adding or removing one frame would invalidate the existing cache (because
494
+ // every position would shift).
495
+ cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
496
+
497
+ for (i=0; i<limit && cfp != top_sentinel; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
487
498
  if (cfp->iseq && !cfp->pc) {
488
499
  // Fix: Do nothing -- this frame should not be used
489
500
  //
@@ -41,7 +41,6 @@ rb_nativethread_id_t pthread_id_for(VALUE thread);
41
41
  bool is_current_thread_holding_the_gvl(void);
42
42
  current_gvl_owner gvl_owner(void);
43
43
  uint64_t native_thread_id_for(VALUE thread);
44
- ptrdiff_t stack_depth_for(VALUE thread);
45
44
  void ddtrace_thread_list(VALUE result_array);
46
45
  bool is_thread_alive(VALUE thread);
47
46
  VALUE thread_name_for(VALUE thread);
@@ -72,7 +72,7 @@ NORETURN(void raise_syserr(
72
72
  // reference a valid object (in which case value is not changed).
73
73
  //
74
74
  // Note: GVL can be released and other threads may get to run before this method returns
75
- bool ruby_ref_from_id(size_t id, VALUE *value);
75
+ bool ruby_ref_from_id(VALUE obj_id, VALUE *value);
76
76
 
77
77
  // Native wrapper to get the approximate/estimated current size of the passed
78
78
  // object.
@@ -26,7 +26,9 @@ if ENV['DD_NO_EXTENSION'].to_s.strip.downcase == 'true'
26
26
  end
27
27
  skip_building_extension!('current Ruby VM is not supported') if RUBY_ENGINE != 'ruby'
28
28
  skip_building_extension!('Microsoft Windows is not supported') if Gem.win_platform?
29
- skip_building_extension!('issue setting up `libdatadog` gem') if Datadog::LibdatadogExtconfHelpers.libdatadog_issue?
29
+
30
+ libdatadog_issue = Datadog::LibdatadogExtconfHelpers.load_libdatadog_or_get_issue
31
+ skip_building_extension!("issue setting up `libdatadog` gem: #{libdatadog_issue}") if libdatadog_issue
30
32
 
31
33
  require 'mkmf'
32
34
 
@@ -5,6 +5,8 @@ require 'pathname'
5
5
 
6
6
  module Datadog
7
7
  # Contains a bunch of shared helpers that get used during building of extensions that link to libdatadog
8
+ #
9
+ # Note: Specs for this file currently live in `spec/datadog/profiling/native_extension_helpers_spec.rb`.
8
10
  module LibdatadogExtconfHelpers
9
11
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
10
12
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
@@ -122,9 +124,17 @@ module Datadog
122
124
  end
123
125
  end
124
126
 
125
- def self.libdatadog_issue?
126
- try_loading_libdatadog { |_exception| return true }
127
- Libdatadog.pkgconfig_folder.nil?
127
+ # Note: This helper is currently only used in the `libdatadog_api/extconf.rb` BUT still lives here to enable testing.
128
+ def self.load_libdatadog_or_get_issue
129
+ try_loading_libdatadog do |exception|
130
+ return "There was an error loading `libdatadog`: #{exception.class} #{exception.message}"
131
+ end
132
+
133
+ unless Libdatadog.pkgconfig_folder
134
+ "The `libdatadog` gem installed on your system is missing binaries for your platform variant. " \
135
+ "Your platform: " \
136
+ "`#{Libdatadog.current_platform}`; available binaries: `#{Libdatadog.available_binaries.join("`, `")}`"
137
+ end
128
138
  end
129
139
  end
130
140
  end
@@ -10,6 +10,7 @@ module Datadog
10
10
  GRAPE_ROUTE_KEY = 'grape.routing_args'
11
11
  RAILS_ROUTE_KEY = 'action_dispatch.route_uri_pattern'
12
12
  RAILS_ROUTES_KEY = 'action_dispatch.routes'
13
+ RAILS_PATH_PARAMS_KEY = 'action_dispatch.request.path_parameters'
13
14
  RAILS_FORMAT_SUFFIX = '(.:format)'
14
15
 
15
16
  # HACK: We rely on the fact that each contrib will modify `request.env`
@@ -36,6 +37,11 @@ module Datadog
36
37
  # "/users/:id(.:format)"
37
38
  #
38
39
  # WARNING: This method works only *after* the request has been routed.
40
+ #
41
+ # WARNING: In Rails > 7.1 when a route was not found,
42
+ # action_dispatch.route_uri_pattern will not be set.
43
+ # In Rails < 7.1 it also will not be set even if a route was found,
44
+ # but in this case action_dispatch.request.path_parameters won't be empty.
39
45
  def self.route_pattern(request)
40
46
  if request.env.key?(GRAPE_ROUTE_KEY)
41
47
  pattern = request.env[GRAPE_ROUTE_KEY][:route_info]&.pattern&.origin
@@ -45,7 +51,7 @@ module Datadog
45
51
  "#{request.script_name}#{pattern}"
46
52
  elsif request.env.key?(RAILS_ROUTE_KEY)
47
53
  request.env[RAILS_ROUTE_KEY].delete_suffix(RAILS_FORMAT_SUFFIX)
48
- elsif request.env.key?(RAILS_ROUTES_KEY)
54
+ elsif request.env.key?(RAILS_ROUTES_KEY) && !request.env.fetch(RAILS_PATH_PARAMS_KEY, {}).empty?
49
55
  pattern = request.env[RAILS_ROUTES_KEY].router
50
56
  .recognize(request) { |route, _| break route.path.spec.to_s }
51
57
 
@@ -4,6 +4,7 @@ require_relative 'security_engine/engine'
4
4
  require_relative 'security_engine/runner'
5
5
  require_relative 'processor/rule_loader'
6
6
  require_relative 'actions_handler'
7
+ require_relative 'thread_safe_ref'
7
8
 
8
9
  module Datadog
9
10
  module AppSec
@@ -74,25 +75,14 @@ module Datadog
74
75
  def initialize(security_engine:, telemetry:)
75
76
  @security_engine = security_engine
76
77
  @telemetry = telemetry
77
-
78
- @mutex = Mutex.new
79
78
  end
80
79
 
81
80
  def reconfigure!
82
- @mutex.synchronize do
83
- security_engine.reconfigure!
84
- end
85
- end
86
-
87
- def reconfigure_lock(&block)
88
- @mutex.synchronize(&block)
81
+ security_engine.reconfigure!
89
82
  end
90
83
 
91
84
  def shutdown!
92
- @mutex.synchronize do
93
- security_engine.finalize!
94
- @security_engine = nil
95
- end
85
+ # no-op
96
86
  end
97
87
  end
98
88
  end
@@ -37,6 +37,7 @@ module Datadog
37
37
  @events = []
38
38
  @waf_runner = waf_runner
39
39
  @metrics = Metrics::Collector.new
40
+ @interrupted = false
40
41
  end
41
42
 
42
43
  def run_waf(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
@@ -55,6 +56,22 @@ module Datadog
55
56
  result
56
57
  end
57
58
 
59
+ def mark_as_interrupted!
60
+ @interrupted = true
61
+ end
62
+
63
+ def interrupted?
64
+ @interrupted
65
+ end
66
+
67
+ def waf_runner_ruleset_version
68
+ @waf_runner.ruleset_version
69
+ end
70
+
71
+ def waf_runner_known_addresses
72
+ @waf_runner.waf_addresses
73
+ end
74
+
58
75
  def extract_schema
59
76
  @waf_runner.run({'waf.context.processor' => {'extract-schema' => true}}, {})
60
77
  end
@@ -66,6 +83,12 @@ module Datadog
66
83
  Metrics::Exporter.export_rasp_metrics(@metrics.rasp, @span)
67
84
  end
68
85
 
86
+ def export_request_telemetry
87
+ return if @trace.nil?
88
+
89
+ Metrics::TelemetryExporter.export_waf_request_metrics(@metrics.waf, self)
90
+ end
91
+
69
92
  def finalize!
70
93
  @waf_runner.finalize!
71
94
  end
@@ -3,6 +3,7 @@
3
3
  require_relative '../ext'
4
4
  require_relative '../configuration'
5
5
  require_relative '../data_extractor'
6
+ require_relative '../../../trace_keeper'
6
7
 
7
8
  module Datadog
8
9
  module AppSec
@@ -25,7 +26,7 @@ module Datadog
25
26
  return result
26
27
  end
27
28
 
28
- context.trace.keep!
29
+ TraceKeeper.keep!(context.trace)
29
30
 
30
31
  if result
31
32
  record_successful_signin(context, resource)
@@ -3,6 +3,7 @@
3
3
  require_relative '../ext'
4
4
  require_relative '../configuration'
5
5
  require_relative '../data_extractor'
6
+ require_relative '../../../trace_keeper'
6
7
 
7
8
  module Datadog
8
9
  module AppSec
@@ -26,7 +27,7 @@ module Datadog
26
27
 
27
28
  next yield(resource) if resource.new_record? && block_given?
28
29
 
29
- context.trace.keep!
30
+ TraceKeeper.keep!(context.trace)
30
31
  record_successful_signup(context, resource)
31
32
  Instrumentation.gateway.push('appsec.events.user_lifecycle', Ext::EVENT_SIGNUP)
32
33
 
@@ -1,4 +1,3 @@
1
- # rubocop:disable Naming/FileName
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'excon'
@@ -1,4 +1,3 @@
1
- # rubocop:disable Naming/FileName
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require_relative '../../event'
@@ -46,33 +46,22 @@ module Datadog
46
46
  boot = Datadog::Core::Remote::Tie.boot
47
47
  Datadog::Core::Remote::Tie::Tracing.tag(boot, active_span)
48
48
 
49
- security_engine = nil
50
- ready = false
51
- ctx = nil
52
-
53
49
  # For a given request, keep using the first Rack stack scope for
54
50
  # nested apps. Don't set `context` local variable so that on popping
55
51
  # out of this nested stack we don't finalize the parent's context
56
52
  return @app.call(env) if active_context(env)
57
53
 
58
- Datadog::AppSec.reconfigure_lock do
59
- security_engine = Datadog::AppSec.security_engine
60
-
61
- if security_engine
62
- ctx = Datadog::AppSec::Context.activate(
63
- Datadog::AppSec::Context.new(active_trace, active_span, security_engine.new_runner)
64
- )
65
-
66
- env[Datadog::AppSec::Ext::CONTEXT_KEY] = ctx
67
- ready = true
68
- end
69
- end
54
+ security_engine = Datadog::AppSec.security_engine
70
55
 
71
56
  # TODO: handle exceptions, except for @app.call
57
+ return @app.call(env) unless security_engine
72
58
 
73
- return @app.call(env) unless ready
59
+ ctx = Datadog::AppSec::Context.activate(
60
+ Datadog::AppSec::Context.new(active_trace, active_span, security_engine.new_runner)
61
+ )
62
+ env[Datadog::AppSec::Ext::CONTEXT_KEY] = ctx
74
63
 
75
- add_appsec_tags(security_engine, ctx)
64
+ add_appsec_tags(ctx)
76
65
  add_request_tags(ctx, env)
77
66
 
78
67
  http_response = nil
@@ -97,6 +86,7 @@ module Datadog
97
86
  end
98
87
 
99
88
  if interrupt_params
89
+ ctx.mark_as_interrupted!
100
90
  http_response = AppSec::Response.from_interrupt_params(interrupt_params, env['HTTP_ACCEPT']).to_rack
101
91
  end
102
92
 
@@ -128,6 +118,8 @@ module Datadog
128
118
  ensure
129
119
  if ctx
130
120
  ctx.export_metrics
121
+ ctx.export_request_telemetry
122
+
131
123
  Datadog::AppSec::Context.deactivate
132
124
  end
133
125
  end
@@ -156,7 +148,7 @@ module Datadog
156
148
  end
157
149
 
158
150
  # standard:disable Metrics/MethodLength
159
- def add_appsec_tags(security_engine, context)
151
+ def add_appsec_tags(context)
160
152
  span = context.span
161
153
  trace = context.trace
162
154
 
@@ -166,15 +158,15 @@ module Datadog
166
158
  span.set_tag('_dd.runtime_family', 'ruby')
167
159
  span.set_tag('_dd.appsec.waf.version', Datadog::AppSec::WAF::VERSION::BASE_STRING)
168
160
 
169
- if security_engine.ruleset_version
170
- span.set_tag('_dd.appsec.event_rules.version', security_engine.ruleset_version)
161
+ if context.waf_runner_ruleset_version
162
+ span.set_tag('_dd.appsec.event_rules.version', context.waf_runner_ruleset_version)
171
163
 
172
164
  unless @oneshot_tags_sent
173
165
  # Small race condition, but it's inoccuous: worst case the tags
174
166
  # are sent a couple of times more than expected
175
167
  @oneshot_tags_sent = true
176
168
 
177
- span.set_tag('_dd.appsec.event_rules.addresses', JSON.dump(security_engine.waf_addresses))
169
+ span.set_tag('_dd.appsec.event_rules.addresses', JSON.dump(context.waf_runner_known_addresses))
178
170
 
179
171
  # Ensure these tags reach the backend
180
172
  trace.keep!
@@ -16,6 +16,7 @@ module Datadog
16
16
  gateway = Instrumentation.gateway
17
17
 
18
18
  watch_request_action(gateway)
19
+ watch_response_body_json(gateway)
19
20
  end
20
21
 
21
22
  def watch_request_action(gateway = Instrumentation.gateway)
@@ -29,18 +30,38 @@ module Datadog
29
30
 
30
31
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
31
32
 
32
- if result.match? || !result.derivatives.empty?
33
+ if result.match?
33
34
  context.events.push(
34
35
  AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
35
36
  )
37
+
38
+ AppSec::Event.tag_and_keep!(context, result)
39
+ AppSec::ActionsHandler.handle(result.actions)
36
40
  end
37
41
 
42
+ stack.call(gateway_request.request)
43
+ end
44
+ end
45
+
46
+ def watch_response_body_json(gateway = Instrumentation.gateway)
47
+ gateway.watch('rails.response.body.json', :appsec) do |stack, container|
48
+ context = container.context
49
+
50
+ persistent_data = {
51
+ 'server.response.body' => container.data
52
+ }
53
+ result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
54
+
38
55
  if result.match?
56
+ context.events.push(
57
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
58
+ )
59
+
39
60
  AppSec::Event.tag_and_keep!(context, result)
40
61
  AppSec::ActionsHandler.handle(result.actions)
41
62
  end
42
63
 
43
- stack.call(gateway_request.request)
64
+ stack.call(container)
44
65
  end
45
66
  end
46
67
  end
@@ -8,6 +8,8 @@ require_relative '../rack/request_middleware'
8
8
  require_relative '../rack/request_body_middleware'
9
9
  require_relative 'gateway/watcher'
10
10
  require_relative 'gateway/request'
11
+ require_relative 'patches/render_to_body_patch'
12
+ require_relative 'patches/process_action_patch'
11
13
 
12
14
  require_relative '../../../tracing/contrib/rack/middlewares'
13
15
 
@@ -17,6 +19,7 @@ module Datadog
17
19
  module Rails
18
20
  # Patcher for AppSec on Rails
19
21
  module Patcher
22
+ GUARD_ACTION_CONTROLLER_ONCE_PER_APP = Hash.new { |h, key| h[key] = Datadog::Core::Utils::OnlyOnce.new }
20
23
  BEFORE_INITIALIZE_ONLY_ONCE_PER_APP = Hash.new { |h, key| h[key] = Datadog::Core::Utils::OnlyOnce.new }
21
24
  AFTER_INITIALIZE_ONLY_ONCE_PER_APP = Hash.new { |h, key| h[key] = Datadog::Core::Utils::OnlyOnce.new }
22
25
 
@@ -34,6 +37,7 @@ module Datadog
34
37
  Gateway::Watcher.watch
35
38
  patch_before_initialize
36
39
  patch_after_initialize
40
+ patch_action_controller
37
41
 
38
42
  Patcher.instance_variable_set(:@patched, true)
39
43
  end
@@ -49,7 +53,8 @@ module Datadog
49
53
  # Middleware must be added before the application is initialized.
50
54
  # Otherwise the middleware stack will be frozen.
51
55
  add_middleware(app) if Datadog.configuration.tracing[:rails][:middleware]
52
- patch_process_action
56
+
57
+ ::ActionController::Metal.prepend(Patches::ProcessActionPatch)
53
58
  end
54
59
  end
55
60
 
@@ -65,31 +70,6 @@ module Datadog
65
70
  end
66
71
  end
67
72
 
68
- # Hook into ActionController::Instrumentation#process_action, which encompasses action filters
69
- module ProcessActionPatch
70
- def process_action(*args)
71
- env = request.env
72
-
73
- context = env[Datadog::AppSec::Ext::CONTEXT_KEY]
74
-
75
- return super unless context
76
-
77
- # TODO: handle exceptions, except for super
78
-
79
- gateway_request = Gateway::Request.new(request)
80
-
81
- http_response, _gateway_request = Instrumentation.gateway.push('rails.request.action', gateway_request) do
82
- super
83
- end
84
-
85
- http_response
86
- end
87
- end
88
-
89
- def patch_process_action
90
- ::ActionController::Metal.prepend(ProcessActionPatch)
91
- end
92
-
93
73
  def include_middleware?(middleware, app)
94
74
  found = false
95
75
 
@@ -143,6 +123,14 @@ module Datadog
143
123
  end
144
124
  end
145
125
 
126
+ def patch_action_controller
127
+ ::ActiveSupport.on_load(:action_controller) do
128
+ GUARD_ACTION_CONTROLLER_ONCE_PER_APP[self].run do
129
+ ::ActionController::Base.prepend(Patches::RenderToBodyPatch)
130
+ end
131
+ end
132
+ end
133
+
146
134
  def setup_security
147
135
  Datadog::AppSec::Contrib::Rails::Framework.setup
148
136
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Contrib
6
+ module Rails
7
+ module Patches
8
+ # Hook into ActionController::Instrumentation#process_action, which encompasses action filters
9
+ module ProcessActionPatch
10
+ def process_action(*args)
11
+ context = request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
12
+ return super unless context
13
+
14
+ # TODO: handle exceptions, except for super
15
+ gateway_request = Gateway::Request.new(request)
16
+ http_response, _gateway_request = Instrumentation.gateway.push('rails.request.action', gateway_request) do
17
+ super
18
+ end
19
+
20
+ http_response
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../utils/hash_coercion'
4
+ require_relative '../../../instrumentation/gateway/argument'
5
+
6
+ module Datadog
7
+ module AppSec
8
+ module Contrib
9
+ module Rails
10
+ module Patches
11
+ # A patch targeting `AbstractController::Rendering#render_to_body`
12
+ # method to capture JSON response body right before it is serialized.
13
+ module RenderToBodyPatch
14
+ def render_to_body(options = {})
15
+ return super unless options.key?(:json)
16
+
17
+ context = request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
18
+ return super unless context
19
+
20
+ data = Utils::HashCoercion.coerce(options[:json])
21
+ return super unless data
22
+
23
+ container = Instrumentation::Gateway::DataContainer.new(data, context: context)
24
+ Instrumentation.gateway.push('rails.response.body.json', container)
25
+
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,4 +1,3 @@
1
- # rubocop:disable Naming/FileName
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require_relative '../../event'
@@ -17,6 +17,7 @@ module Datadog
17
17
 
18
18
  watch_request_dispatch(gateway)
19
19
  watch_request_routed(gateway)
20
+ watch_response_body_json(gateway)
20
21
  end
21
22
 
22
23
  def watch_request_dispatch(gateway = Instrumentation.gateway)
@@ -67,6 +68,28 @@ module Datadog
67
68
  stack.call(gateway_request.request)
68
69
  end
69
70
  end
71
+
72
+ def watch_response_body_json(gateway = Instrumentation.gateway)
73
+ gateway.watch('sinatra.response.body.json', :appsec) do |stack, container|
74
+ context = container.context
75
+
76
+ persistent_data = {
77
+ 'server.response.body' => container.data
78
+ }
79
+ result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
80
+
81
+ if result.match?
82
+ context.events.push(
83
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
84
+ )
85
+
86
+ AppSec::Event.tag_and_keep!(context, result)
87
+ AppSec::ActionsHandler.handle(result.actions)
88
+ end
89
+
90
+ stack.call(container)
91
+ end
92
+ end
70
93
  end
71
94
  end
72
95
  end