dd-trace 5.110.0 → 5.112.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 (164) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/README.md +13 -8
  3. package/ci/init.js +21 -2
  4. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  5. package/ext/tags.js +2 -0
  6. package/index.d.ts +34 -1
  7. package/initialize.mjs +5 -6
  8. package/loader-hook.mjs +76 -55
  9. package/package.json +37 -34
  10. package/packages/datadog-instrumentations/src/ai.js +45 -0
  11. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  12. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  13. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  14. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  15. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  16. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  17. package/packages/datadog-instrumentations/src/fs.js +8 -6
  18. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  25. package/packages/datadog-instrumentations/src/jest.js +713 -63
  26. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  27. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  28. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  29. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  30. package/packages/datadog-instrumentations/src/mongoose.js +13 -2
  31. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  32. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
  33. package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
  34. package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
  35. package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
  36. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  37. package/packages/datadog-plugin-ai/src/index.js +1 -1
  38. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  39. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  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 +19 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  44. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  45. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  46. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  47. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  48. package/packages/datadog-plugin-cucumber/src/index.js +15 -24
  49. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
  50. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  51. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  52. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  53. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  54. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  55. package/packages/datadog-plugin-graphql/src/execute.js +639 -12
  56. package/packages/datadog-plugin-graphql/src/index.js +37 -9
  57. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  58. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  59. package/packages/datadog-plugin-graphql/src/validate.js +17 -6
  60. package/packages/datadog-plugin-http/src/index.js +6 -8
  61. package/packages/datadog-plugin-jest/src/index.js +31 -15
  62. package/packages/datadog-plugin-mocha/src/index.js +40 -15
  63. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  64. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  65. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  66. package/packages/datadog-plugin-openai/src/services.js +2 -2
  67. package/packages/datadog-plugin-playwright/src/index.js +4 -3
  68. package/packages/datadog-plugin-vitest/src/index.js +120 -72
  69. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  70. package/packages/dd-trace/src/aiguard/index.js +9 -14
  71. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  72. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  73. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  74. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  75. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  76. package/packages/dd-trace/src/appsec/channels.js +3 -1
  77. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  78. package/packages/dd-trace/src/appsec/graphql.js +14 -11
  79. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
  80. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  81. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  82. package/packages/dd-trace/src/appsec/index.js +10 -1
  83. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  84. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  85. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  86. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  87. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  90. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  91. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
  92. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  93. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  94. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  96. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
  97. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  98. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  99. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  100. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  101. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  102. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  103. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  104. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  105. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  106. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  107. package/packages/dd-trace/src/config/defaults.js +3 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
  109. package/packages/dd-trace/src/config/helper.js +1 -0
  110. package/packages/dd-trace/src/config/index.js +35 -22
  111. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  112. package/packages/dd-trace/src/config/remote_config.js +1 -1
  113. package/packages/dd-trace/src/config/supported-configurations.json +127 -56
  114. package/packages/dd-trace/src/constants.js +7 -0
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  116. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  117. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  118. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  119. package/packages/dd-trace/src/llmobs/index.js +1 -1
  120. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  121. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  122. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  123. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  124. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  125. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  126. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  127. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  128. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  129. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  130. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  131. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  132. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  133. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  135. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  136. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  137. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  138. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  139. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  140. package/packages/dd-trace/src/plugin_manager.js +23 -7
  141. package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
  142. package/packages/dd-trace/src/plugins/index.js +2 -0
  143. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  144. package/packages/dd-trace/src/plugins/util/test.js +15 -9
  145. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  146. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  147. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  148. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  149. package/packages/dd-trace/src/proxy.js +8 -8
  150. package/packages/dd-trace/src/span_processor.js +5 -6
  151. package/packages/dd-trace/src/span_stats.js +96 -78
  152. package/packages/dd-trace/src/startup-log.js +2 -1
  153. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  154. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  155. package/packages/dd-trace/src/telemetry/index.js +2 -2
  156. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  157. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  158. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  159. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  160. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  161. package/vendor/dist/protobufjs/index.js +1 -1
  162. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  163. package/vendor/dist/shell-quote/index.js +1 -1
  164. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
@@ -11,45 +11,59 @@ const doGenerateAfterChannel = channel('dd-trace:vercel-ai:doGenerate:after')
11
11
  const doStreamBeforeChannel = channel('dd-trace:vercel-ai:doStream:before')
12
12
  const doStreamAfterChannel = channel('dd-trace:vercel-ai:doStream:after')
13
13
 
14
+ let isEnabled = false
15
+ let aiguard
16
+ let opts
17
+
14
18
  /**
15
19
  * Subscribes AI Guard to Vercel AI lifecycle channels.
16
20
  *
17
- * @param {object} aiguard
21
+ * @param {object} aiguardInstance
18
22
  * @param {boolean} block
19
- * @returns {() => void}
20
23
  */
21
- function enable (aiguard, block) {
22
- const opts = { block, source: SOURCE_AUTO, integration: 'ai' }
23
-
24
- function onBefore (ctx) {
25
- pushEvaluation(ctx, aiguard, convertVercelPromptToMessages(ctx.prompt), opts)
26
- }
27
-
28
- function onGenerateAfter (ctx) {
29
- const inputMessages = convertVercelPromptToMessages(ctx.prompt)
30
- if (!inputMessages.length || !ctx.result?.content?.length) return
31
-
32
- pushEvaluation(ctx, aiguard, buildOutputMessages(inputMessages, ctx.result.content), opts)
33
- }
24
+ function enable (aiguardInstance, block) {
25
+ if (isEnabled) return
34
26
 
35
- function onStreamAfter (ctx) {
36
- const inputMessages = convertVercelPromptToMessages(ctx.prompt)
37
- if (!inputMessages.length || !ctx.chunks?.length) return
38
-
39
- pushEvaluation(ctx, aiguard, buildOutputMessages(inputMessages, getStreamContent(ctx.chunks)), opts)
40
- }
27
+ aiguard = aiguardInstance
28
+ opts = { block, source: SOURCE_AUTO, integration: 'ai' }
41
29
 
42
30
  doGenerateBeforeChannel.subscribe(onBefore)
43
31
  doGenerateAfterChannel.subscribe(onGenerateAfter)
44
32
  doStreamBeforeChannel.subscribe(onBefore)
45
33
  doStreamAfterChannel.subscribe(onStreamAfter)
46
34
 
47
- return function disable () {
48
- doGenerateBeforeChannel.unsubscribe(onBefore)
49
- doGenerateAfterChannel.unsubscribe(onGenerateAfter)
50
- doStreamBeforeChannel.unsubscribe(onBefore)
51
- doStreamAfterChannel.unsubscribe(onStreamAfter)
52
- }
35
+ isEnabled = true
36
+ }
37
+
38
+ function disable () {
39
+ if (!isEnabled) return
40
+
41
+ doGenerateBeforeChannel.unsubscribe(onBefore)
42
+ doGenerateAfterChannel.unsubscribe(onGenerateAfter)
43
+ doStreamBeforeChannel.unsubscribe(onBefore)
44
+ doStreamAfterChannel.unsubscribe(onStreamAfter)
45
+
46
+ aiguard = undefined
47
+ opts = undefined
48
+ isEnabled = false
49
+ }
50
+
51
+ function onBefore (ctx) {
52
+ pushEvaluation(ctx, aiguard, convertVercelPromptToMessages(ctx.prompt), opts)
53
+ }
54
+
55
+ function onGenerateAfter (ctx) {
56
+ const inputMessages = convertVercelPromptToMessages(ctx.prompt)
57
+ if (!inputMessages.length || !ctx.result?.content?.length) return
58
+
59
+ pushEvaluation(ctx, aiguard, buildOutputMessages(inputMessages, ctx.result.content), opts)
60
+ }
61
+
62
+ function onStreamAfter (ctx) {
63
+ const inputMessages = convertVercelPromptToMessages(ctx.prompt)
64
+ if (!inputMessages.length || !ctx.chunks?.length) return
65
+
66
+ pushEvaluation(ctx, aiguard, buildOutputMessages(inputMessages, getStreamContent(ctx.chunks)), opts)
53
67
  }
54
68
 
55
69
  /**
@@ -75,4 +89,4 @@ function getStreamContent (chunks) {
75
89
  return text ? [{ type: 'text', text }] : []
76
90
  }
77
91
 
78
- module.exports = { enable }
92
+ module.exports = { enable, disable }
@@ -93,14 +93,14 @@ class AIGuard extends NoopAIGuard {
93
93
  constructor (tracer, config) {
94
94
  super()
95
95
 
96
- if (!config.apiKey || !config.DD_APP_KEY) {
96
+ if (!config.DD_API_KEY || !config.DD_APP_KEY) {
97
97
  log.error('AIGuard: missing api and/or app keys, use env DD_API_KEY and DD_APP_KEY')
98
98
  this.#initialized = false
99
99
  return
100
100
  }
101
101
  this.#tracer = tracer
102
102
  this.#headers = {
103
- 'DD-API-KEY': config.apiKey,
103
+ 'DD-API-KEY': config.DD_API_KEY,
104
104
  'DD-APPLICATION-KEY': config.DD_APP_KEY,
105
105
  'DD-AI-GUARD-VERSION': tracerVersion,
106
106
  'DD-AI-GUARD-SOURCE': 'SDK',
@@ -31,11 +31,11 @@ class NoopTTLCache {
31
31
  }
32
32
 
33
33
  function configure ({ appsec, apmTracingEnabled }) {
34
- enabled = appsec.apiSecurity.enabled
34
+ enabled = appsec.DD_API_SECURITY_ENABLED
35
35
  asmStandaloneEnabled = apmTracingEnabled === false
36
- sampledRequests = appsec.apiSecurity.sampleDelay === 0
36
+ sampledRequests = appsec.DD_API_SECURITY_SAMPLE_DELAY === 0
37
37
  ? new NoopTTLCache()
38
- : new TTLCache({ max: MAX_SIZE, ttl: appsec.apiSecurity.sampleDelay * 1000 })
38
+ : new TTLCache({ max: MAX_SIZE, ttl: appsec.DD_API_SECURITY_SAMPLE_DELAY * 1000 })
39
39
  }
40
40
 
41
41
  function disable () {
@@ -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
  }
@@ -72,14 +72,17 @@ function enterInApolloServerCoreRequest () {
72
72
 
73
73
  function enterInApolloRequest () {
74
74
  const req = getActiveRequest()
75
+ if (!req) return
75
76
 
76
- const requestData = graphqlRequestData.get(req)
77
- if (requestData) {
78
- // Set isInGraphqlRequest=true since this function only runs for GraphQL requests
79
- // This works for both Apollo v4 (middleware) and v5 (HTTP server) contexts
80
- requestData.isInGraphqlRequest = true
81
- addSpecificEndpoint(req, specificBlockingTypes.GRAPHQL)
77
+ let requestData = graphqlRequestData.get(req)
78
+ if (!requestData) {
79
+ // executeHTTPGraphQLRequest is the GraphQL request boundary, so seed here
80
+ // when no upstream hook (express4 middleware, drainHttpServer) has run.
81
+ requestData = { blocked: false }
82
+ graphqlRequestData.set(req, requestData)
82
83
  }
84
+ requestData.isInGraphqlRequest = true
85
+ addSpecificEndpoint(req, specificBlockingTypes.GRAPHQL)
83
86
  }
84
87
 
85
88
  function beforeWriteApolloGraphqlResponse ({ abortController, abortData }) {
@@ -153,11 +156,11 @@ function disableApollo () {
153
156
  }
154
157
 
155
158
  function enableGraphql () {
156
- startGraphqlResolve.subscribe(onGraphqlStartResolve)
159
+ startGraphqlResolver.subscribe(onGraphqlStartResolver)
157
160
  }
158
161
 
159
162
  function disableGraphql () {
160
- if (startGraphqlResolve.hasSubscribers) startGraphqlResolve.unsubscribe(onGraphqlStartResolve)
163
+ if (startGraphqlResolver.hasSubscribers) startGraphqlResolver.unsubscribe(onGraphqlStartResolver)
161
164
  }
162
165
 
163
166
  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,39 +41,25 @@ 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
  }
34
48
  }
35
-
36
- return store
37
- }
38
-
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
49
  }
54
50
 
55
51
  this.addSub('datadog:mongodb:collection:filter:start', onStart)
56
52
 
57
- this.addSub('datadog:mongoose:model:filter:start', onStartAndEnterWithStore)
58
- this.addSub('datadog:mongoose:model:filter:finish', onFinish)
53
+ // A mongoose query builds, executes, and reaches the driver in separate async
54
+ // steps. The filters are analyzed at the build, but the marker is bound around
55
+ // the execution channel, where the driver call still runs under the parent store.
56
+ this.addSub('datadog:mongoose:model:filter:start', onStart)
57
+ this.addBind('datadog:mongoose:model:filter:exec', markExecStore)
59
58
 
59
+ // mquery analyzes filters at the prepare step and marks the execution scope at
60
+ // tracing start, where the deferred driver call runs under the marked store.
60
61
  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)
62
+ this.addBind('tracing:datadog:mquery:filter:start', markExecStore)
63
63
  }
64
64
 
65
65
  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,
@@ -43,6 +45,7 @@ const { isBlocked, block, callBlockDelegation, setTemplates, getBlockingAction }
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()
@@ -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
 
@@ -544,6 +551,8 @@ function disable () {
544
551
  if (stripeCheckoutSessionCreate.hasSubscribers) stripeCheckoutSessionCreate.unsubscribe(onStripeCheckoutSessionCreate)
545
552
  if (stripePaymentIntentCreate.hasSubscribers) stripePaymentIntentCreate.unsubscribe(onStripePaymentIntentCreate)
546
553
  if (stripeConstructEvent.hasSubscribers) stripeConstructEvent.unsubscribe(onStripeConstructEvent)
554
+ if (lambdaStartInvocation.hasSubscribers) lambdaStartInvocation.unsubscribe(lambda.onLambdaStartInvocation)
555
+ if (lambdaEndInvocation.hasSubscribers) lambdaEndInvocation.unsubscribe(lambda.onLambdaEndInvocation)
547
556
  }
548
557
 
549
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()
@@ -27,7 +27,7 @@ const metricsStoreMap = new WeakMap()
27
27
  let enabled = false
28
28
 
29
29
  function enable (config) {
30
- enabled = config.telemetry?.enabled && config.telemetry?.metrics
30
+ enabled = config.telemetry.DD_INSTRUMENTATION_TELEMETRY_ENABLED && config.telemetry.DD_TELEMETRY_METRICS_ENABLED
31
31
  }
32
32
 
33
33
  function disable () {
@@ -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
  }