dd-trace 5.109.0 → 5.111.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 (208) hide show
  1. package/README.md +13 -8
  2. package/ci/init.js +25 -5
  3. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +79 -24
  6. package/initialize.mjs +5 -6
  7. package/loader-hook.mjs +154 -47
  8. package/package.json +20 -16
  9. package/packages/datadog-esbuild/index.js +26 -0
  10. package/packages/datadog-esbuild/src/utils.js +46 -3
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  13. package/packages/datadog-instrumentations/src/fs.js +8 -6
  14. package/packages/datadog-instrumentations/src/graphql.js +26 -484
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  17. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  19. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  23. package/packages/datadog-instrumentations/src/koa.js +3 -2
  24. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  25. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  26. package/packages/datadog-instrumentations/src/mongoose.js +14 -2
  27. package/packages/datadog-instrumentations/src/next.js +10 -10
  28. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  29. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  30. package/packages/datadog-instrumentations/src/restify.js +4 -3
  31. package/packages/datadog-instrumentations/src/router.js +18 -5
  32. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  33. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  34. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
  35. package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
  36. package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
  37. package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
  38. package/packages/datadog-instrumentations/src/vitest.js +11 -1598
  39. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  45. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
  46. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -5
  48. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  49. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  50. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +627 -11
  52. package/packages/datadog-plugin-graphql/src/index.js +20 -8
  53. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  54. package/packages/datadog-plugin-graphql/src/validate.js +16 -5
  55. package/packages/datadog-plugin-http/src/client.js +8 -3
  56. package/packages/datadog-plugin-http/src/index.js +6 -8
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-jest/src/index.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  60. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  61. package/packages/datadog-plugin-openai/src/services.js +2 -2
  62. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  63. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  64. package/packages/datadog-plugin-undici/src/index.js +5 -2
  65. package/packages/datadog-plugin-vitest/src/index.js +76 -64
  66. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  67. package/packages/datadog-webpack/index.js +17 -1
  68. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  69. package/packages/dd-trace/src/aiguard/sdk.js +15 -3
  70. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  71. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  72. package/packages/dd-trace/src/appsec/channels.js +3 -1
  73. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  74. package/packages/dd-trace/src/appsec/graphql.js +5 -5
  75. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
  76. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  77. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  78. package/packages/dd-trace/src/appsec/index.js +20 -7
  79. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  80. package/packages/dd-trace/src/appsec/reporter.js +50 -10
  81. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  82. package/packages/dd-trace/src/appsec/telemetry/index.js +27 -1
  83. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  84. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  85. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  86. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
  87. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
  90. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  91. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
  92. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  93. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  94. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  95. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  96. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  97. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  98. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  99. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  100. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  101. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  102. package/packages/dd-trace/src/config/defaults.js +25 -5
  103. package/packages/dd-trace/src/config/generated-config-types.d.ts +596 -58
  104. package/packages/dd-trace/src/config/helper.js +134 -33
  105. package/packages/dd-trace/src/config/index.js +39 -23
  106. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  107. package/packages/dd-trace/src/config/parsers.js +19 -1
  108. package/packages/dd-trace/src/config/remote_config.js +1 -1
  109. package/packages/dd-trace/src/config/supported-configurations.json +109 -57
  110. package/packages/dd-trace/src/constants.js +7 -0
  111. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  112. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
  113. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  114. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  115. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  116. package/packages/dd-trace/src/debugger/index.js +5 -0
  117. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  118. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  119. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  120. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  121. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  122. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  123. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  124. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  125. package/packages/dd-trace/src/guardrails/index.js +8 -5
  126. package/packages/dd-trace/src/index.js +6 -4
  127. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  128. package/packages/dd-trace/src/llmobs/index.js +17 -6
  129. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  130. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  131. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/sdk.js +8 -6
  134. package/packages/dd-trace/src/llmobs/span_processor.js +5 -1
  135. package/packages/dd-trace/src/llmobs/tagger.js +58 -3
  136. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  137. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  138. package/packages/dd-trace/src/log/index.js +18 -20
  139. package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
  140. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  141. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  142. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  143. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  145. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  146. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  147. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  148. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  149. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  150. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  151. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  152. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +44 -38
  153. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  154. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  155. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  156. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  157. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  158. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  159. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  160. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  161. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  162. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  163. package/packages/dd-trace/src/plugin_manager.js +14 -8
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -4
  165. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  166. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  167. package/packages/dd-trace/src/plugins/util/test.js +91 -9
  168. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  169. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  170. package/packages/dd-trace/src/priority_sampler.js +1 -13
  171. package/packages/dd-trace/src/profiling/config.js +129 -198
  172. package/packages/dd-trace/src/profiling/constants.js +4 -7
  173. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  174. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  175. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  176. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  177. package/packages/dd-trace/src/profiling/oom.js +71 -0
  178. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  179. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  180. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  181. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  182. package/packages/dd-trace/src/proxy.js +8 -8
  183. package/packages/dd-trace/src/ritm.js +4 -2
  184. package/packages/dd-trace/src/serverless.js +1 -2
  185. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  186. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  187. package/packages/dd-trace/src/span_format.js +2 -1
  188. package/packages/dd-trace/src/span_processor.js +11 -6
  189. package/packages/dd-trace/src/span_stats.js +96 -78
  190. package/packages/dd-trace/src/startup-log.js +2 -1
  191. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  192. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  193. package/packages/dd-trace/src/telemetry/index.js +2 -2
  194. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  195. package/packages/dd-trace/src/telemetry/send-data.js +20 -18
  196. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  197. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  198. package/packages/dd-trace/src/tracer.js +15 -1
  199. package/packages/dd-trace/src/util.js +14 -0
  200. package/register.js +58 -1
  201. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  202. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  203. package/vendor/dist/pprof-format/index.js +1 -1
  204. package/vendor/dist/protobufjs/index.js +1 -1
  205. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  206. package/vendor/dist/shell-quote/index.js +1 -1
  207. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
  208. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -27,6 +27,8 @@ module.exports = {
27
27
  httpClientResponseFinish: dc.channel('apm:http:client:response:finish'),
28
28
  incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
29
29
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
30
+ lambdaStartInvocation: dc.channel('datadog:lambda:start-invocation'),
31
+ lambdaEndInvocation: dc.channel('datadog:lambda:end-invocation'),
30
32
  multerParser: dc.channel('datadog:multer:read:finish'),
31
33
  mysql2OuterQueryStart: dc.channel('datadog:mysql2:outerquery:start'),
32
34
  nextBodyParsed: dc.channel('apm:next:body-parsed'),
@@ -43,7 +45,7 @@ module.exports = {
43
45
  routerParam: dc.channel('datadog:router:param:start'),
44
46
  setCookieChannel: dc.channel('datadog:iast:set-cookie'),
45
47
  setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start'),
46
- startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
48
+ startGraphqlResolver: dc.channel('datadog:graphql:resolver:start'),
47
49
  stripeCheckoutSessionCreate: dc.channel('datadog:stripe:checkoutSession:create:finish'),
48
50
  stripeConstructEvent: dc.channel('datadog:stripe:constructEvent:finish'),
49
51
  stripePaymentIntentCreate: dc.channel('datadog:stripe:paymentIntent:create:finish'),
@@ -41,7 +41,7 @@ function enable (_config) {
41
41
  downstreamAnalysisCount = new WeakMap()
42
42
  responseBodyIgnoredCount = new WeakMap()
43
43
 
44
- const bodyAnalysisSampleRate = config.appsec.apiSecurity?.downstreamBodyAnalysisSampleRate
44
+ const bodyAnalysisSampleRate = config.appsec.DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE
45
45
  samplingRate = Math.min(Math.max(bodyAnalysisSampleRate, 0), 1)
46
46
 
47
47
  if (samplingRate !== bodyAnalysisSampleRate) {
@@ -105,7 +105,7 @@ function recordResponseBodyIgnored (req, tag) {
105
105
  * @returns {boolean} whether downstream response body should be collected for AppSec.
106
106
  */
107
107
  function evaluateResponseBodyCollection (originatingReq, res) {
108
- const maxBytes = config.appsec.apiSecurity.maxDownstreamBodyBytes
108
+ const maxBytes = config.appsec.DD_API_SECURITY_MAX_DOWNSTREAM_BODY_BYTES
109
109
 
110
110
  const mime = extractMimeType(res.headers?.['content-type'])
111
111
  if (!mime || !SUPPORTED_RESPONSE_BODY_MIME_TYPES.has(mime)) {
@@ -137,7 +137,7 @@ function shouldSampleBody (req) {
137
137
  globalRequestCounter = (globalRequestCounter + 1n) & UINT64_MAX
138
138
 
139
139
  const currentCount = bodyAnalysisCount.get(req) || 0
140
- if (currentCount >= config.appsec.apiSecurity?.maxDownstreamRequestBodyAnalysis) {
140
+ if (currentCount >= config.appsec.DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS) {
141
141
  return false
142
142
  }
143
143
 
@@ -166,7 +166,7 @@ function isRedirectResponse (res) {
166
166
  * @param {object} ctx http client instrumentation context (mutated).
167
167
  */
168
168
  function planResponseBodyCollection (originatingReq, res, ctx) {
169
- if (!config?.appsec.apiSecurity) {
169
+ if (!config) {
170
170
  return
171
171
  }
172
172
 
@@ -12,7 +12,7 @@ const {
12
12
  const waf = require('./waf')
13
13
  const addresses = require('./addresses')
14
14
  const {
15
- startGraphqlResolve,
15
+ startGraphqlResolver,
16
16
  graphqlMiddlewareChannel,
17
17
  apolloHttpServerChannel,
18
18
  apolloChannel,
@@ -32,7 +32,7 @@ function disable () {
32
32
  disableGraphql()
33
33
  }
34
34
 
35
- function onGraphqlStartResolve ({ context, resolverInfo }) {
35
+ function onGraphqlStartResolver ({ abortController, resolverInfo }) {
36
36
  const req = getActiveRequest()
37
37
 
38
38
  if (!req) return
@@ -46,7 +46,7 @@ function onGraphqlStartResolve ({ context, resolverInfo }) {
46
46
  if (requestData?.isInGraphqlRequest) {
47
47
  requestData.blocked = true
48
48
  requestData.wafAction = blockingAction
49
- context?.abortController?.abort()
49
+ abortController?.abort()
50
50
  }
51
51
  }
52
52
  }
@@ -153,11 +153,11 @@ function disableApollo () {
153
153
  }
154
154
 
155
155
  function enableGraphql () {
156
- startGraphqlResolve.subscribe(onGraphqlStartResolve)
156
+ startGraphqlResolver.subscribe(onGraphqlStartResolver)
157
157
  }
158
158
 
159
159
  function disableGraphql () {
160
- if (startGraphqlResolve.hasSubscribers) startGraphqlResolve.unsubscribe(onGraphqlStartResolve)
160
+ if (startGraphqlResolver.hasSubscribers) startGraphqlResolver.unsubscribe(onGraphqlStartResolver)
161
161
  }
162
162
 
163
163
  module.exports = {
@@ -15,6 +15,20 @@ const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose', 'mq
15
15
  const SAFE_OPERATORS = new Set(['$eq', '$gt', '$gte', '$in', '$lt', '$lte', '$ne', '$nin',
16
16
  '$exists', '$type', '$mod', '$bitsAllClear', '$bitsAllSet', '$bitsAnyClear', '$bitsAnySet'])
17
17
 
18
+ // Scope a marked child store for a query's deferred execution so the nested mongodb
19
+ // driver query skips re-analysis. `bindStore` enters the child only for that async
20
+ // scope and restores the parent on its own, so no marker can be stranded for a later
21
+ // request to inherit, and two concurrent queries of the same request each branch from
22
+ // the shared parent rather than from each other.
23
+ function markExecStore () {
24
+ const store = storage('legacy').getStore()
25
+ if (store && !store.nosqlAnalyzed && getIastContext(store)) {
26
+ return { ...store, nosqlAnalyzed: true }
27
+ }
28
+
29
+ return store
30
+ }
31
+
18
32
  class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
19
33
  constructor () {
20
34
  super(NOSQL_MONGODB_INJECTION)
@@ -27,7 +41,7 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
27
41
  // Anything that accesses the storage is context dependent
28
42
  const onStart = ({ filters }) => {
29
43
  const store = storage('legacy').getStore()
30
- if (store && !store.nosqlAnalyzed && filters?.length) {
44
+ if (store && !store.nosqlAnalyzed && filters?.length && getIastContext(store)) {
31
45
  for (const filter of filters) {
32
46
  this.analyze({ filter }, store)
33
47
  }
@@ -36,30 +50,23 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
36
50
  return store
37
51
  }
38
52
 
39
- const onStartAndEnterWithStore = (message) => {
40
- const store = onStart(message || {})
41
- if (store) {
42
- storage('legacy').enterWith({ ...store, nosqlAnalyzed: true, nosqlParentStore: store })
43
- }
44
- }
45
-
46
- // Anything that accesses the storage is context dependent
47
- // eslint-disable-next-line unicorn/consistent-function-scoping
48
- const onFinish = () => {
49
- const store = storage('legacy').getStore()
50
- if (store?.nosqlParentStore) {
51
- storage('legacy').enterWith(store.nosqlParentStore)
52
- }
53
+ // mquery analyzes and marks on the same channel: its tracing start both reaches
54
+ // the driver scope and is the first place the filters are available.
55
+ const onStartAndBind = (message) => {
56
+ onStart(message)
57
+ return markExecStore()
53
58
  }
54
59
 
55
60
  this.addSub('datadog:mongodb:collection:filter:start', onStart)
56
61
 
57
- this.addSub('datadog:mongoose:model:filter:start', onStartAndEnterWithStore)
58
- this.addSub('datadog:mongoose:model:filter:finish', onFinish)
62
+ // A mongoose query builds, executes, and reaches the driver in separate async
63
+ // steps. The filters are analyzed at the build, but the marker is bound around
64
+ // the execution channel, where the driver call still runs under the parent store.
65
+ this.addSub('datadog:mongoose:model:filter:start', onStart)
66
+ this.addBind('datadog:mongoose:model:filter:exec', markExecStore)
59
67
 
60
68
  this.addSub('datadog:mquery:filter:prepare', onStart)
61
- this.addSub('tracing:datadog:mquery:filter:start', onStartAndEnterWithStore)
62
- this.addSub('tracing:datadog:mquery:filter:asyncEnd', onFinish)
69
+ this.addBind('tracing:datadog:mquery:filter:start', onStartAndBind)
63
70
  }
64
71
 
65
72
  configureSanitizers () {
@@ -21,10 +21,10 @@ let controlsKeys
21
21
  let hooks
22
22
 
23
23
  function configure (iastConfig) {
24
- if (!iastConfig?.securityControlsConfiguration) return
24
+ if (!iastConfig.DD_IAST_SECURITY_CONTROLS_CONFIGURATION) return
25
25
 
26
26
  try {
27
- controls = parse(iastConfig.securityControlsConfiguration)
27
+ controls = parse(iastConfig.DD_IAST_SECURITY_CONTROLS_CONFIGURATION)
28
28
  if (controls?.size > 0) {
29
29
  hooks = new WeakSet()
30
30
  controlsKeys = [...controls.keys()]
@@ -6,7 +6,8 @@ const { initRequestNamespace, finalizeRequestNamespace, globalNamespace } = requ
6
6
 
7
7
  class Telemetry {
8
8
  configure (config, verbosity) {
9
- const telemetryAndMetricsEnabled = config?.telemetry?.enabled && config.telemetry.metrics
9
+ const telemetryAndMetricsEnabled = config.telemetry.DD_INSTRUMENTATION_TELEMETRY_ENABLED &&
10
+ config.telemetry.DD_TELEMETRY_METRICS_ENABLED
10
11
 
11
12
  this.verbosity = telemetryAndMetricsEnabled ? getVerbosity(verbosity) : Verbosity.OFF
12
13
  this.enabled = this.verbosity !== Verbosity.OFF
@@ -16,6 +16,8 @@ const {
16
16
  fastifyCookieParser,
17
17
  incomingHttpRequestStart,
18
18
  incomingHttpRequestEnd,
19
+ lambdaStartInvocation,
20
+ lambdaEndInvocation,
19
21
  passportVerify,
20
22
  passportUser,
21
23
  expressSession,
@@ -38,11 +40,12 @@ const waf = require('./waf')
38
40
  const addresses = require('./addresses')
39
41
  const Reporter = require('./reporter')
40
42
  const appsecTelemetry = require('./telemetry')
41
- const apiSecuritySampler = require('./api_security_sampler')
43
+ const apiSecurity = require('./api_security')
42
44
  const { isBlocked, block, callBlockDelegation, setTemplates, getBlockingAction } = require('./blocking')
43
45
  const { getActiveRequest } = require('./store')
44
46
  const UserTracking = require('./user_tracking')
45
47
  const graphql = require('./graphql')
48
+ const lambda = require('./lambda')
46
49
  const rasp = require('./rasp')
47
50
 
48
51
  const responseAnalyzedSet = new WeakSet()
@@ -71,7 +74,7 @@ function enable (_config) {
71
74
 
72
75
  Reporter.init(_config.appsec, _config.inferredProxyServicesEnabled)
73
76
 
74
- apiSecuritySampler.configure(_config)
77
+ apiSecurity.configure(_config)
75
78
 
76
79
  UserTracking.setCollectionMode(_config.appsec.eventTracking.mode, false)
77
80
 
@@ -99,11 +102,15 @@ function enable (_config) {
99
102
  stripeCheckoutSessionCreate.subscribe(onStripeCheckoutSessionCreate)
100
103
  stripePaymentIntentCreate.subscribe(onStripePaymentIntentCreate)
101
104
  stripeConstructEvent.subscribe(onStripeConstructEvent)
105
+ lambdaStartInvocation.subscribe(lambda.onLambdaStartInvocation)
106
+ lambdaEndInvocation.subscribe(lambda.onLambdaEndInvocation)
102
107
 
103
108
  isEnabled = true
104
109
  config = _config
105
110
  } catch (err) {
106
- if (!IS_SERVERLESS) {
111
+ if (IS_SERVERLESS) {
112
+ log.debug('[ASM] Serverless mode: suppressing error log, calling disable()')
113
+ } else {
107
114
  log.error('[ASM] Unable to start AppSec', err)
108
115
  }
109
116
 
@@ -233,14 +240,18 @@ function incomingHttpEndTranslator ({ req, res }) {
233
240
  // This hook runs before span finish, so ensure route/endpoint tags are available before API Security sampling runs.
234
241
  web.setRouteOrEndpointTag(req)
235
242
 
236
- if (apiSecuritySampler.sampleRequest(req, res, true)) {
243
+ const apiSecSamplingDecision = apiSecurity.sampleRequest(req, res, true)
244
+ if (apiSecSamplingDecision === apiSecurity.SamplingDecision.SAMPLE) {
237
245
  persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
238
246
  }
239
247
 
248
+ let wafResult
240
249
  if (!isEmpty(persistent)) {
241
- waf.run({ persistent }, req)
250
+ wafResult = waf.run({ persistent }, req)
242
251
  }
243
252
 
253
+ apiSecurity.reportRequest(req, apiSecSamplingDecision, wafResult)
254
+
244
255
  waf.disposeContext(req)
245
256
 
246
257
  const storedHeaders = storedResponseHeaders.get(req) || {}
@@ -339,7 +350,7 @@ function onRequestProcessParams ({ req, res, abortController, params }) {
339
350
 
340
351
  function onResponseBody ({ req, res, body }) {
341
352
  if (!body || typeof body !== 'object') return
342
- if (!apiSecuritySampler.sampleRequest(req, res)) return
353
+ if (apiSecurity.sampleRequest(req, res) !== apiSecurity.SamplingDecision.SAMPLE) return
343
354
 
344
355
  // we don't support blocking at this point, so no results needed
345
356
  waf.run({
@@ -513,7 +524,7 @@ function disable () {
513
524
 
514
525
  appsecRemoteConfig.disableWafUpdate()
515
526
 
516
- apiSecuritySampler.disable()
527
+ apiSecurity.disable()
517
528
 
518
529
  // Channel#unsubscribe() is undefined for non active channels
519
530
  if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
@@ -540,6 +551,8 @@ function disable () {
540
551
  if (stripeCheckoutSessionCreate.hasSubscribers) stripeCheckoutSessionCreate.unsubscribe(onStripeCheckoutSessionCreate)
541
552
  if (stripePaymentIntentCreate.hasSubscribers) stripePaymentIntentCreate.unsubscribe(onStripePaymentIntentCreate)
542
553
  if (stripeConstructEvent.hasSubscribers) stripeConstructEvent.unsubscribe(onStripeConstructEvent)
554
+ if (lambdaStartInvocation.hasSubscribers) lambdaStartInvocation.unsubscribe(lambda.onLambdaStartInvocation)
555
+ if (lambdaEndInvocation.hasSubscribers) lambdaEndInvocation.unsubscribe(lambda.onLambdaEndInvocation)
543
556
  }
544
557
 
545
558
  /**
@@ -0,0 +1,135 @@
1
+ 'use strict'
2
+
3
+ const { HTTP_CLIENT_IP } = require('../../../../ext/tags')
4
+
5
+ const log = require('../log')
6
+ const addresses = require('./addresses')
7
+ const Reporter = require('./reporter')
8
+ const waf = require('./waf')
9
+
10
+ // Tracks spans for which start-invocation has been processed, so that
11
+ // end-invocation can gate correctly
12
+ const activeInvocations = new WeakSet()
13
+
14
+ /**
15
+ * Maps pre-extracted HTTP data from the Lambda event to WAF addresses,
16
+ * runs the WAF, and reports results on the span.
17
+ *
18
+ * @param {{ span: object, headers: Record<string, string>, method: string, path: string,
19
+ * query: Record<string, string | string[]> | undefined, body: string | object | undefined,
20
+ * isBase64Encoded: boolean, clientIp: string | undefined,
21
+ * pathParams: Record<string, string> | undefined,
22
+ * cookies: Record<string, string> | undefined,
23
+ * route: string | undefined }} data
24
+ */
25
+ function onLambdaStartInvocation (data) {
26
+ try {
27
+ const { span, headers, method, path, query, body, clientIp, pathParams, cookies } = data
28
+
29
+ if (!span) {
30
+ log.warn('[ASM] No span provided in Lambda start invocation')
31
+ return
32
+ }
33
+
34
+ activeInvocations.add(span)
35
+
36
+ span.addTags({
37
+ '_dd.appsec.enabled': 1,
38
+ '_dd.runtime_family': 'nodejs',
39
+ })
40
+
41
+ const persistent = {}
42
+
43
+ if (path) {
44
+ persistent[addresses.HTTP_INCOMING_URL] = path
45
+ }
46
+
47
+ if (method) {
48
+ persistent[addresses.HTTP_INCOMING_METHOD] = method
49
+ }
50
+
51
+ if (headers) {
52
+ // Cookie header is already stripped by the Lambda layer's event-data-extractor
53
+ persistent[addresses.HTTP_INCOMING_HEADERS] = headers
54
+ }
55
+
56
+ if (clientIp) {
57
+ span.setTag(HTTP_CLIENT_IP, clientIp)
58
+ persistent[addresses.HTTP_CLIENT_IP] = clientIp
59
+ }
60
+
61
+ if (query) {
62
+ persistent[addresses.HTTP_INCOMING_QUERY] = query
63
+ }
64
+
65
+ if (body !== undefined && body !== null) {
66
+ persistent[addresses.HTTP_INCOMING_BODY] = body
67
+ }
68
+
69
+ if (pathParams) {
70
+ persistent[addresses.HTTP_INCOMING_PARAMS] = pathParams
71
+ }
72
+
73
+ if (cookies) {
74
+ persistent[addresses.HTTP_INCOMING_COOKIES] = cookies
75
+ }
76
+
77
+ waf.run({ persistent }, span, undefined, span)
78
+ } catch (err) {
79
+ log.error('[ASM] Error in Lambda start-invocation handler', err)
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Maps response data to WAF addresses, runs a final WAF pass,
85
+ * disposes the WAF context, and finishes the request report.
86
+ *
87
+ * @param {{ span: object, statusCode: string | undefined,
88
+ * responseHeaders: Record<string, string> | undefined }} data
89
+ */
90
+ function onLambdaEndInvocation (data) {
91
+ try {
92
+ const { span, statusCode, responseHeaders } = data
93
+
94
+ if (!span) {
95
+ log.warn('[ASM] No span provided in Lambda end invocation')
96
+ return
97
+ }
98
+
99
+ if (!activeInvocations.has(span)) {
100
+ return
101
+ }
102
+
103
+ activeInvocations.delete(span)
104
+
105
+ let hasPersistentData = false
106
+ const persistent = {}
107
+
108
+ if (statusCode) {
109
+ persistent[addresses.HTTP_INCOMING_RESPONSE_CODE] = String(statusCode)
110
+ hasPersistentData = true
111
+ }
112
+
113
+ if (responseHeaders) {
114
+ const filteredHeaders = { ...responseHeaders }
115
+ delete filteredHeaders['set-cookie']
116
+ persistent[addresses.HTTP_INCOMING_RESPONSE_HEADERS] = filteredHeaders
117
+ hasPersistentData = true
118
+ }
119
+
120
+ if (hasPersistentData) {
121
+ waf.run({ persistent }, span, undefined, span)
122
+ }
123
+
124
+ waf.disposeContext(span)
125
+
126
+ Reporter.finishRequest(span, null, {}, undefined, span)
127
+ } catch (err) {
128
+ log.error('[ASM] Error in Lambda end-invocation handler', err)
129
+ }
130
+ }
131
+
132
+ module.exports = {
133
+ onLambdaStartInvocation,
134
+ onLambdaEndInvocation,
135
+ }
@@ -318,13 +318,21 @@ function reportWafConfigUpdate (product, rcConfigId, diagnostics, wafVersion) {
318
318
  }
319
319
  }
320
320
 
321
- function reportMetrics (metrics, raspRule, req) {
321
+ /**
322
+ * @param {object} metrics - WAF run metrics
323
+ * @param {string} [raspRule] - RASP rule identifier
324
+ * @param {object} [req] - Request key (plain object for lambda)
325
+ * @param {object} [rootSpan] - Root span (required for lambda)
326
+ */
327
+ function reportMetrics (metrics, raspRule, req, rootSpan) {
322
328
  if (!req) {
323
329
  req = getActiveRequest()
324
330
  }
325
- const rootSpan = req && web.root(req)
331
+ if (!rootSpan) {
332
+ rootSpan = req && web.root(req)
333
+ }
326
334
 
327
- if (!rootSpan) return
335
+ if (!req || !rootSpan) return
328
336
 
329
337
  if (metrics.rulesVersion) {
330
338
  rootSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
@@ -353,13 +361,26 @@ function reportTruncationMetrics (rootSpan, metrics) {
353
361
  }
354
362
  }
355
363
 
356
- function reportAttack ({ events: attackData, actions }, req) {
364
+ // NOTE: `req` in the WAF execution path may be any object, not necessarily
365
+ // an HTTP IncomingMessage. In Lambda, it is a plain context key ({}) with no
366
+ // HTTP properties. Always guard HTTP-specific property access
367
+ // See tests in lambda.spec.js for enforcement.
368
+
369
+ /**
370
+ * @param {{ events: Array, actions: object }} result - WAF result with attack data
371
+ * @param {object} [req] - Request key. May be an HTTP IncomingMessage or a plain object (Lambda)
372
+ * @param {object} [rootSpan] - Root span (required for lambda)
373
+ */
374
+ function reportAttack ({ events: attackData, actions }, req, rootSpan) {
357
375
  if (!req) {
358
376
  req = getActiveRequest()
359
377
  }
360
378
 
361
- const rootSpan = web.root(req)
362
- if (!rootSpan) return
379
+ if (!rootSpan) {
380
+ rootSpan = web.root(req)
381
+ }
382
+
383
+ if (!req || !rootSpan) return
363
384
 
364
385
  const spanContext = rootSpan.context()
365
386
 
@@ -494,14 +515,21 @@ function isSchemaAttribute (attribute) {
494
515
  return attribute.startsWith('_dd.appsec.s.')
495
516
  }
496
517
 
497
- function reportAttributes (attributes, req) {
518
+ /**
519
+ * @param {object} [attributes] - WAF result attributes
520
+ * @param {object} [req] - Request key (plain object for lambda)
521
+ * @param {object} [rootSpan] - Root span (required for lambda)
522
+ */
523
+ function reportAttributes (attributes, req, rootSpan) {
498
524
  if (!attributes) return
499
525
 
500
526
  if (!req) {
501
527
  req = getActiveRequest()
502
528
  }
503
529
 
504
- const rootSpan = web.root(req)
530
+ if (!rootSpan) {
531
+ rootSpan = web.root(req)
532
+ }
505
533
 
506
534
  if (!rootSpan) return
507
535
 
@@ -517,8 +545,17 @@ function reportAttributes (attributes, req) {
517
545
  rootSpan.addTags(tags)
518
546
  }
519
547
 
520
- function finishRequest (req, res, storedResponseHeaders, requestBody) {
521
- const rootSpan = web.root(req)
548
+ /**
549
+ * @param {object} [req] - Request key (null for Lambda)
550
+ * @param {object} [res] - Response object (null for lambda)
551
+ * @param {object} storedResponseHeaders
552
+ * @param {object} [requestBody]
553
+ * @param {object} [rootSpan] - Root span (required for lambda)
554
+ */
555
+ function finishRequest (req, res, storedResponseHeaders, requestBody, rootSpan) {
556
+ if (!rootSpan) {
557
+ rootSpan = web.root(req)
558
+ }
522
559
  if (!rootSpan) return
523
560
 
524
561
  if (metricsQueue.size) {
@@ -569,6 +606,8 @@ function finishRequest (req, res, storedResponseHeaders, requestBody) {
569
606
  rootSpan.setTag('_dd.appsec.rasp.rule.eval', metrics.raspEvalCount)
570
607
  }
571
608
 
609
+ if (!req) return
610
+
572
611
  incrementWafRequestsMetric(req)
573
612
 
574
613
  const tags = rootSpan.context().getTags()
@@ -615,4 +654,5 @@ module.exports = {
615
654
  finishRequest,
616
655
  mapHeaderAndTags,
617
656
  truncateRequestBody,
657
+ isSchemaAttribute,
618
658
  }
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const telemetryMetrics = require('../../telemetry/metrics')
4
+
5
+ const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
6
+
7
+ const normalizedFrameworkCache = new Map()
8
+
9
+ function normalizeFramework (framework) {
10
+ let normalized = normalizedFrameworkCache.get(framework)
11
+ if (normalized === undefined) {
12
+ normalized = framework ? framework.toLowerCase().replaceAll(' ', '_') : 'unknown'
13
+ normalizedFrameworkCache.set(framework, normalized)
14
+ }
15
+ return normalized
16
+ }
17
+
18
+ function incrementApiSecRequestSchema (framework) {
19
+ appsecMetrics.count('api_security.request.schema', { framework: normalizeFramework(framework) }).inc()
20
+ }
21
+
22
+ function incrementApiSecRequestNoSchema (framework) {
23
+ appsecMetrics.count('api_security.request.no_schema', { framework: normalizeFramework(framework) }).inc()
24
+ }
25
+
26
+ function incrementApiSecMissingRoute (framework) {
27
+ appsecMetrics.count('api_security.missing_route', { framework: normalizeFramework(framework) }).inc()
28
+ }
29
+
30
+ module.exports = {
31
+ incrementApiSecRequestSchema,
32
+ incrementApiSecRequestNoSchema,
33
+ incrementApiSecMissingRoute,
34
+ }
@@ -1,5 +1,10 @@
1
1
  'use strict'
2
2
 
3
+ const {
4
+ incrementApiSecMissingRoute,
5
+ incrementApiSecRequestNoSchema,
6
+ incrementApiSecRequestSchema,
7
+ } = require('./api_security')
3
8
  const { DD_TELEMETRY_REQUEST_METRICS } = require('./common')
4
9
  const { incrementMissingUserId, incrementMissingUserLogin, incrementSdkEvent } = require('./user')
5
10
  const {
@@ -22,7 +27,7 @@ const metricsStoreMap = new WeakMap()
22
27
  let enabled = false
23
28
 
24
29
  function enable (config) {
25
- enabled = config.telemetry?.enabled && config.telemetry?.metrics
30
+ enabled = config.telemetry.DD_INSTRUMENTATION_TELEMETRY_ENABLED && config.telemetry.DD_TELEMETRY_METRICS_ENABLED
26
31
  }
27
32
 
28
33
  function disable () {
@@ -155,6 +160,24 @@ function incrementSdkEventMetric (eventType, sdkVersion) {
155
160
  incrementSdkEvent(eventType, sdkVersion)
156
161
  }
157
162
 
163
+ function incrementApiSecRequestSchemaMetric (framework) {
164
+ if (!enabled) return
165
+
166
+ incrementApiSecRequestSchema(framework)
167
+ }
168
+
169
+ function incrementApiSecRequestNoSchemaMetric (framework) {
170
+ if (!enabled) return
171
+
172
+ incrementApiSecRequestNoSchema(framework)
173
+ }
174
+
175
+ function incrementApiSecMissingRouteMetric (framework) {
176
+ if (!enabled) return
177
+
178
+ incrementApiSecMissingRoute(framework)
179
+ }
180
+
158
181
  function getRequestMetrics (req) {
159
182
  if (req) {
160
183
  const store = getStore(req)
@@ -179,6 +202,9 @@ module.exports = {
179
202
  incrementMissingUserLoginMetric,
180
203
  incrementMissingUserIdMetric,
181
204
  incrementSdkEventMetric,
205
+ incrementApiSecRequestSchemaMetric,
206
+ incrementApiSecRequestNoSchemaMetric,
207
+ incrementApiSecMissingRouteMetric,
182
208
 
183
209
  getRequestMetrics,
184
210
  }
@@ -110,7 +110,19 @@ function removeConfig (configPath) {
110
110
  }
111
111
  }
112
112
 
113
- function run (data, req, raspRule) {
113
+ // `req` in the WAF execution path may be any object, not necessarily
114
+ // an HTTP IncomingMessage (it is a plain object for lambda).
115
+ // Always guard HTTP-specific property access.
116
+ // @see packages/dd-trace/test/appsec/lambda.spec.js
117
+
118
+ /**
119
+ * @param {object} data - WAF address data ({ persistent, ephemeral })
120
+ * @param {object} req - Request key for WAF context lookup. May be an HTTP
121
+ * IncomingMessage or a plain object (Lambda invocation key).
122
+ * @param {string} [raspRule] - RASP rule identifier
123
+ * @param {object} [rootSpan] - Root span to tag (required for Lambda)
124
+ */
125
+ function run (data, req, raspRule, rootSpan) {
114
126
  if (!req) {
115
127
  req = getActiveRequest()
116
128
  if (!req) {
@@ -120,12 +132,12 @@ function run (data, req, raspRule) {
120
132
  }
121
133
 
122
134
  const wafContext = waf.wafManager.getWAFContext(req)
123
- const result = wafContext.run(data, raspRule, req)
135
+ const result = wafContext.run(data, raspRule, req, rootSpan)
124
136
 
125
137
  if (result?.keep) {
126
138
  if (limiter.isAllowed()) {
127
- const rootSpan = web.root(req)
128
- keepTrace(rootSpan, ASM)
139
+ const span = rootSpan || web.root(req)
140
+ keepTrace(span, ASM)
129
141
  } else {
130
142
  updateRateLimitedMetric(req)
131
143
  }