dd-trace 5.88.0 → 5.90.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 (103) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/ext/tags.js +2 -0
  3. package/index.d.ts +40 -0
  4. package/package.json +18 -14
  5. package/packages/datadog-instrumentations/src/azure-durable-functions.js +75 -0
  6. package/packages/datadog-instrumentations/src/cucumber.js +40 -1
  7. package/packages/datadog-instrumentations/src/elasticsearch.js +12 -3
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +26 -111
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/{compiler.js → orchestrion/compiler.js} +5 -5
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/index.js +43 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/matcher.js +49 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/transformer.js +121 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/{transforms.js → orchestrion/transforms.js} +6 -6
  15. package/packages/datadog-instrumentations/src/jest.js +123 -43
  16. package/packages/datadog-instrumentations/src/mocha/main.js +10 -4
  17. package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
  18. package/packages/datadog-instrumentations/src/mocha/worker.js +10 -2
  19. package/packages/datadog-instrumentations/src/playwright.js +20 -2
  20. package/packages/datadog-instrumentations/src/prisma.js +4 -2
  21. package/packages/datadog-instrumentations/src/vitest.js +16 -0
  22. package/packages/datadog-plugin-apollo/src/gateway/execute.js +8 -0
  23. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +5 -0
  24. package/packages/datadog-plugin-apollo/src/gateway/plan.js +8 -0
  25. package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +5 -0
  26. package/packages/datadog-plugin-apollo/src/gateway/request.js +4 -3
  27. package/packages/datadog-plugin-apollo/src/gateway/validate.js +4 -3
  28. package/packages/datadog-plugin-apollo/src/index.js +28 -0
  29. package/packages/datadog-plugin-azure-durable-functions/src/index.js +49 -0
  30. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +47 -6
  31. package/packages/datadog-plugin-cypress/src/source-map-utils.js +297 -0
  32. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  33. package/packages/datadog-plugin-jest/src/index.js +6 -0
  34. package/packages/datadog-plugin-playwright/src/index.js +35 -8
  35. package/packages/dd-trace/src/aiguard/noop.js +1 -1
  36. package/packages/dd-trace/src/aiguard/sdk.js +18 -5
  37. package/packages/dd-trace/src/appsec/api_security_sampler.js +22 -1
  38. package/packages/dd-trace/src/appsec/index.js +11 -1
  39. package/packages/dd-trace/src/appsec/reporter.js +28 -11
  40. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  41. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  42. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -0
  43. package/packages/dd-trace/src/config/index.js +3 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +17 -0
  45. package/packages/dd-trace/src/constants.js +1 -0
  46. package/packages/dd-trace/src/datastreams/checkpointer.js +13 -0
  47. package/packages/dd-trace/src/datastreams/index.js +3 -0
  48. package/packages/dd-trace/src/datastreams/manager.js +9 -0
  49. package/packages/dd-trace/src/datastreams/processor.js +126 -3
  50. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +1 -8
  51. package/packages/dd-trace/src/encode/agentless-json.js +82 -23
  52. package/packages/dd-trace/src/exporters/agent/writer.js +7 -8
  53. package/packages/dd-trace/src/exporters/agentless/index.js +58 -15
  54. package/packages/dd-trace/src/exporters/agentless/writer.js +35 -18
  55. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  56. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +9 -0
  57. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  58. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -0
  59. package/packages/dd-trace/src/pkg.js +1 -1
  60. package/packages/dd-trace/src/plugins/apollo.js +7 -2
  61. package/packages/dd-trace/src/plugins/index.js +1 -0
  62. package/packages/dd-trace/src/plugins/util/ci.js +95 -3
  63. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +36 -2
  64. package/packages/dd-trace/src/plugins/util/web.js +31 -11
  65. package/packages/dd-trace/src/proxy.js +2 -1
  66. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -0
  67. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +4 -0
  68. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +4 -0
  69. package/packages/dd-trace/src/standalone/product.js +2 -1
  70. package/packages/dd-trace/src/startup-log.js +52 -18
  71. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  72. package/vendor/dist/@datadog/source-map/index.js +1 -1
  73. package/vendor/dist/@isaacs/ttlcache/index.js +1 -1
  74. package/vendor/dist/@opentelemetry/core/index.js +1 -1
  75. package/vendor/dist/@opentelemetry/resources/index.js +1 -1
  76. package/vendor/dist/astring/index.js +1 -1
  77. package/vendor/dist/crypto-randomuuid/index.js +1 -1
  78. package/vendor/dist/escape-string-regexp/index.js +1 -1
  79. package/vendor/dist/esquery/index.js +1 -1
  80. package/vendor/dist/ignore/index.js +1 -1
  81. package/vendor/dist/istanbul-lib-coverage/index.js +1 -1
  82. package/vendor/dist/jest-docblock/index.js +1 -1
  83. package/vendor/dist/jsonpath-plus/index.js +1 -1
  84. package/vendor/dist/limiter/index.js +1 -1
  85. package/vendor/dist/lodash.sortby/index.js +1 -1
  86. package/vendor/dist/lru-cache/index.js +1 -1
  87. package/vendor/dist/meriyah/index.js +1 -1
  88. package/vendor/dist/module-details-from-path/index.js +1 -1
  89. package/vendor/dist/mutexify/promise/index.js +1 -1
  90. package/vendor/dist/opentracing/index.js +1 -1
  91. package/vendor/dist/path-to-regexp/index.js +1 -1
  92. package/vendor/dist/pprof-format/index.js +1 -1
  93. package/vendor/dist/protobufjs/index.js +1 -1
  94. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  95. package/vendor/dist/retry/index.js +1 -1
  96. package/vendor/dist/rfdc/index.js +1 -1
  97. package/vendor/dist/semifies/index.js +1 -1
  98. package/vendor/dist/shell-quote/index.js +1 -1
  99. package/vendor/dist/source-map/index.js +1 -1
  100. package/vendor/dist/source-map/lib/util/index.js +1 -1
  101. package/vendor/dist/tlhunter-sorted-set/index.js +1 -1
  102. package/vendor/dist/ttl-set/index.js +1 -1
  103. package/packages/datadog-instrumentations/src/helpers/rewriter/transformer.js +0 -21
@@ -114,11 +114,14 @@ class PlaywrightPlugin extends CiPlugin {
114
114
  })
115
115
 
116
116
  this.addBind('ci:playwright:test-suite:start', (ctx) => {
117
- const { testSuiteAbsolutePath } = ctx
117
+ const { testSuiteAbsolutePath, testSourceFileAbsolutePath } = ctx
118
118
 
119
119
  const store = storage('legacy').getStore()
120
120
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
121
- const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
121
+ const testSourceFile = getTestSuitePath(
122
+ testSourceFileAbsolutePath || testSuiteAbsolutePath,
123
+ this.repositoryRoot
124
+ )
122
125
 
123
126
  const testSuiteMetadata = {
124
127
  ...getTestSuiteCommonTags(
@@ -238,12 +241,15 @@ class PlaywrightPlugin extends CiPlugin {
238
241
  // test_suite_absolute_path is just a hack because in the worker we don't have rootDir and repositoryRoot
239
242
  // but if we pass those the same way we pass `DD_PLAYWRIGHT_WORKER` this is not necessary
240
243
  const testSuitePath = getTestSuitePath(formattedSpan.meta.test_suite_absolute_path, this.rootDir)
241
- const testSourceFile = getTestSuitePath(formattedSpan.meta.test_suite_absolute_path, this.repositoryRoot)
244
+ const testSourceAbsolutePath = formattedSpan.meta.test_source_absolute_path ||
245
+ formattedSpan.meta.test_suite_absolute_path
246
+ const testSourceFile = getTestSuitePath(testSourceAbsolutePath, this.repositoryRoot)
242
247
  // we need to rewrite this because this.rootDir and this.repositoryRoot are not available in the worker
243
248
  formattedSpan.meta[TEST_SUITE] = testSuitePath
244
249
  formattedSpan.meta[TEST_SOURCE_FILE] = testSourceFile
245
250
  formattedSpan.resource = `${testSuitePath}.${formattedSpan.meta[TEST_NAME]}`
246
251
  delete formattedSpan.meta.test_suite_absolute_path
252
+ delete formattedSpan.meta.test_source_absolute_path
247
253
  }
248
254
  formattedTrace.push(formattedSpan)
249
255
  }
@@ -259,20 +265,25 @@ class PlaywrightPlugin extends CiPlugin {
259
265
  const {
260
266
  testName,
261
267
  testSuiteAbsolutePath,
268
+ testSourceFileAbsolutePath,
262
269
  testSourceLine,
263
270
  browserName,
264
271
  isDisabled,
265
272
  } = ctx
266
273
  const store = storage('legacy').getStore()
267
274
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
268
- const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
275
+ const testSourceFile = getTestSuitePath(
276
+ testSourceFileAbsolutePath || testSuiteAbsolutePath,
277
+ this.repositoryRoot
278
+ )
269
279
  const span = this.startTestSpan(
270
280
  testName,
271
281
  testSuiteAbsolutePath,
272
282
  testSuite,
273
283
  testSourceFile,
274
284
  testSourceLine,
275
- browserName
285
+ browserName,
286
+ testSourceFileAbsolutePath
276
287
  )
277
288
 
278
289
  if (isDisabled) {
@@ -408,6 +419,7 @@ class PlaywrightPlugin extends CiPlugin {
408
419
  this.addSub('ci:playwright:test:skip', ({
409
420
  testName,
410
421
  testSuiteAbsolutePath,
422
+ testSourceFileAbsolutePath,
411
423
  testSourceLine,
412
424
  browserName,
413
425
  isNew,
@@ -416,14 +428,18 @@ class PlaywrightPlugin extends CiPlugin {
416
428
  isQuarantined,
417
429
  }) => {
418
430
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
419
- const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
431
+ const testSourceFile = getTestSuitePath(
432
+ testSourceFileAbsolutePath || testSuiteAbsolutePath,
433
+ this.repositoryRoot
434
+ )
420
435
  const span = this.startTestSpan(
421
436
  testName,
422
437
  testSuiteAbsolutePath,
423
438
  testSuite,
424
439
  testSourceFile,
425
440
  testSourceLine,
426
- browserName
441
+ browserName,
442
+ testSourceFileAbsolutePath
427
443
  )
428
444
 
429
445
  span.setTag(TEST_STATUS, 'skip')
@@ -446,7 +462,15 @@ class PlaywrightPlugin extends CiPlugin {
446
462
  }
447
463
 
448
464
  // TODO: this runs both in worker and main process (main process: skipped tests that do not go through _runTest)
449
- startTestSpan (testName, testSuiteAbsolutePath, testSuite, testSourceFile, testSourceLine, browserName) {
465
+ startTestSpan (
466
+ testName,
467
+ testSuiteAbsolutePath,
468
+ testSuite,
469
+ testSourceFile,
470
+ testSourceLine,
471
+ browserName,
472
+ testSourceFileAbsolutePath
473
+ ) {
450
474
  const testSuiteSpan = this._testSuiteSpansByTestSuiteAbsolutePath.get(testSuiteAbsolutePath)
451
475
 
452
476
  const extraTags = {
@@ -462,6 +486,9 @@ class PlaywrightPlugin extends CiPlugin {
462
486
  }
463
487
 
464
488
  extraTags.test_suite_absolute_path = testSuiteAbsolutePath
489
+ if (testSourceFileAbsolutePath) {
490
+ extraTags.test_source_absolute_path = testSourceFileAbsolutePath
491
+ }
465
492
 
466
493
  return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
467
494
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  class NoopAIGuard {
4
4
  evaluate (messages, opts) {
5
- return Promise.resolve({ action: 'ALLOW', reason: 'AI Guard is not enabled' })
5
+ return Promise.resolve({ action: 'ALLOW', reason: 'AI Guard is not enabled', tags: [], sds: [] })
6
6
  }
7
7
  }
8
8
 
@@ -4,6 +4,8 @@ const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: fa
4
4
  const log = require('../log')
5
5
  const telemetryMetrics = require('../telemetry/metrics')
6
6
  const tracerVersion = require('../../../../package.json').version
7
+ const { keepTrace } = require('../priority_sampler')
8
+ const { AI_GUARD } = require('../standalone/product')
7
9
  const NoopAIGuard = require('./noop')
8
10
  const executeRequest = require('./client')
9
11
  const {
@@ -23,11 +25,12 @@ const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
23
25
  const ALLOW = 'ALLOW'
24
26
 
25
27
  class AIGuardAbortError extends Error {
26
- constructor (reason, tags) {
28
+ constructor (reason, tags, sds) {
27
29
  super(reason)
28
30
  this.name = 'AIGuardAbortError'
29
31
  this.reason = reason
30
32
  this.tags = tags
33
+ this.sds = sds || []
31
34
  }
32
35
  }
33
36
 
@@ -135,7 +138,7 @@ class AIGuard extends NoopAIGuard {
135
138
  if (!this.#initialized) {
136
139
  return super.evaluate(messages, opts)
137
140
  }
138
- const { block = false } = opts ?? {}
141
+ const { block = true } = opts ?? {}
139
142
  return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (span) => {
140
143
  const last = messages[messages.length - 1]
141
144
  const target = this.#isToolCall(last) ? 'tool' : 'prompt'
@@ -152,6 +155,12 @@ class AIGuard extends NoopAIGuard {
152
155
  span.meta_struct = {
153
156
  [AI_GUARD_META_STRUCT_KEY]: metaStruct,
154
157
  }
158
+ const rootSpan = span.context()?._trace?.started?.[0]
159
+ if (rootSpan) {
160
+ // keepTrace must be called before executeRequest so the sampling decision
161
+ // is propagated correctly to outgoing HTTP client calls.
162
+ keepTrace(rootSpan, AI_GUARD)
163
+ }
155
164
  let response
156
165
  try {
157
166
  const payload = {
@@ -175,7 +184,7 @@ class AIGuard extends NoopAIGuard {
175
184
  `AI Guard service call failed, status ${response.status}`,
176
185
  { errors: response.body?.errors })
177
186
  }
178
- let action, reason, tags, blockingEnabled
187
+ let action, reason, tags, sdsFindings, blockingEnabled
179
188
  try {
180
189
  const attr = response.body.data.attributes
181
190
  if (!attr.action) {
@@ -184,6 +193,7 @@ class AIGuard extends NoopAIGuard {
184
193
  action = attr.action
185
194
  reason = attr.reason
186
195
  tags = attr.tags
196
+ sdsFindings = attr.sds_findings || []
187
197
  blockingEnabled = attr.is_blocking_enabled ?? false
188
198
  } catch (e) {
189
199
  appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
@@ -198,11 +208,14 @@ class AIGuard extends NoopAIGuard {
198
208
  if (tags?.length > 0) {
199
209
  metaStruct.attack_categories = tags
200
210
  }
211
+ if (sdsFindings?.length > 0) {
212
+ metaStruct.sds = sdsFindings
213
+ }
201
214
  if (shouldBlock) {
202
215
  span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
203
- throw new AIGuardAbortError(reason, tags)
216
+ throw new AIGuardAbortError(reason, tags, sdsFindings)
204
217
  }
205
- return { action, reason, tags }
218
+ return { action, reason, tags, sds: sdsFindings }
206
219
  })
207
220
  }
208
221
  }
@@ -70,8 +70,25 @@ function isSampled (key) {
70
70
  return sampledRequests.has(key)
71
71
  }
72
72
 
73
+ function getRouteOrEndpoint (context, statusCode) {
74
+ // First try to get the route from the context paths
75
+ const route = context?.paths?.join('') || ''
76
+ if (route) {
77
+ return route
78
+ }
79
+
80
+ // If route is not available, fallback to http.endpoint
81
+ if (statusCode !== 404) {
82
+ const endpoint = context?.span?.context()?._tags?.['http.endpoint']
83
+ if (endpoint) {
84
+ return endpoint
85
+ }
86
+ }
87
+
88
+ return ''
89
+ }
90
+
73
91
  function computeKey (req, res) {
74
- const route = web.getContext(req)?.paths?.join('') || ''
75
92
  const method = req.method
76
93
  const status = res.statusCode
77
94
 
@@ -79,6 +96,10 @@ function computeKey (req, res) {
79
96
  log.warn('[ASM] Unsupported groupkey for API security')
80
97
  return null
81
98
  }
99
+
100
+ const context = web.getContext(req)
101
+ const route = getRouteOrEndpoint(context, status)
102
+
82
103
  return method + route + status
83
104
  }
84
105
 
@@ -68,7 +68,7 @@ function enable (_config) {
68
68
 
69
69
  appsecRemoteConfig.enableWafUpdate(_config.appsec)
70
70
 
71
- Reporter.init(_config.appsec)
71
+ Reporter.init(_config.appsec, _config.inferredProxyServicesEnabled)
72
72
 
73
73
  apiSecuritySampler.configure(_config)
74
74
 
@@ -174,6 +174,13 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
174
174
  [HTTP_CLIENT_IP]: clientIp,
175
175
  })
176
176
 
177
+ if (config.inferredProxyServicesEnabled) {
178
+ const context = web.getContext(req)
179
+ if (context?.inferredProxySpan) {
180
+ context.inferredProxySpan.setTag('_dd.appsec.enabled', 1)
181
+ }
182
+ }
183
+
177
184
  const requestHeaders = { ...req.headers }
178
185
  delete requestHeaders.cookie
179
186
 
@@ -226,6 +233,9 @@ function incomingHttpEndTranslator ({ req, res }) {
226
233
  persistent[addresses.HTTP_INCOMING_QUERY] = query
227
234
  }
228
235
 
236
+ // This hook runs before span finish, so ensure route/endpoint tags are available before API Security sampling runs.
237
+ web.setRouteOrEndpointTag(req)
238
+
229
239
  if (apiSecuritySampler.sampleRequest(req, res, true)) {
230
240
  persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
231
241
  }
@@ -34,6 +34,7 @@ const config = {
34
34
  maxHeadersCollected: 0,
35
35
  headersRedaction: false,
36
36
  raspBodyCollection: false,
37
+ inferredProxyServicesEnabled: false,
37
38
  }
38
39
 
39
40
  const metricsQueue = new Map()
@@ -103,11 +104,12 @@ const NON_EXTENDED_REQUEST_HEADERS = new Set([...requestHeadersList, ...eventHea
103
104
  const NON_EXTENDED_RESPONSE_HEADERS = new Set(responseHeaderList)
104
105
  const REDACTED_HEADERS = new Set(redactedHeadersList)
105
106
 
106
- function init (_config) {
107
+ function init (_config, inferredProxyServicesEnabled) {
107
108
  config.headersExtendedCollectionEnabled = _config.extendedHeadersCollection.enabled
108
109
  config.maxHeadersCollected = _config.extendedHeadersCollection.maxHeaders
109
110
  config.headersRedaction = _config.extendedHeadersCollection.redaction
110
111
  config.raspBodyCollection = _config.rasp.bodyCollection
112
+ config.inferredProxyServicesEnabled = inferredProxyServicesEnabled
111
113
  }
112
114
 
113
115
  function formatHeaderName (name) {
@@ -298,9 +300,11 @@ function reportWafConfigUpdate (product, rcConfigId, diagnostics, wafVersion) {
298
300
  }
299
301
  }
300
302
 
301
- function reportMetrics (metrics, raspRule) {
302
- const store = storage('legacy').getStore()
303
- const rootSpan = store?.req && web.root(store.req)
303
+ function reportMetrics (metrics, raspRule, req) {
304
+ if (!req) {
305
+ req = storage('legacy').getStore()?.req
306
+ }
307
+ const rootSpan = req && web.root(req)
304
308
 
305
309
  if (!rootSpan) return
306
310
 
@@ -309,9 +313,9 @@ function reportMetrics (metrics, raspRule) {
309
313
  }
310
314
 
311
315
  if (raspRule) {
312
- updateRaspRequestsMetricTags(metrics, store.req, raspRule)
316
+ updateRaspRequestsMetricTags(metrics, req, raspRule)
313
317
  } else {
314
- updateWafRequestsMetricTags(metrics, store.req)
318
+ updateWafRequestsMetricTags(metrics, req)
315
319
  }
316
320
 
317
321
  reportTruncationMetrics(rootSpan, metrics)
@@ -331,9 +335,11 @@ function reportTruncationMetrics (rootSpan, metrics) {
331
335
  }
332
336
  }
333
337
 
334
- function reportAttack ({ events: attackData, actions }) {
335
- const store = storage('legacy').getStore()
336
- const req = store?.req
338
+ function reportAttack ({ events: attackData, actions }, req) {
339
+ if (!req) {
340
+ req = storage('legacy').getStore()?.req
341
+ }
342
+
337
343
  const rootSpan = web.root(req)
338
344
  if (!rootSpan) return
339
345
 
@@ -362,6 +368,14 @@ function reportAttack ({ events: attackData, actions }) {
362
368
 
363
369
  rootSpan.addTags(newTags)
364
370
 
371
+ // Add _dd.appsec.json tag to inferred proxy span
372
+ if (config.inferredProxyServicesEnabled) {
373
+ const context = web.getContext(req)
374
+ if (context?.inferredProxySpan) {
375
+ context.inferredProxySpan.setTag('_dd.appsec.json', newTags['_dd.appsec.json'])
376
+ }
377
+ }
378
+
365
379
  // TODO this should be deleted in a major
366
380
  if (config.raspBodyCollection && isRaspAttack(attackData)) {
367
381
  reportRequestBody(rootSpan, req.body, true)
@@ -463,10 +477,13 @@ function isSchemaAttribute (attribute) {
463
477
  return attribute.startsWith('_dd.appsec.s.')
464
478
  }
465
479
 
466
- function reportAttributes (attributes) {
480
+ function reportAttributes (attributes, req) {
467
481
  if (!attributes) return
468
482
 
469
- const req = storage('legacy').getStore()?.req
483
+ if (!req) {
484
+ req = storage('legacy').getStore()?.req
485
+ }
486
+
470
487
  const rootSpan = web.root(req)
471
488
 
472
489
  if (!rootSpan) return
@@ -122,7 +122,7 @@ function run (data, req, raspRule) {
122
122
  }
123
123
 
124
124
  const wafContext = waf.wafManager.getWAFContext(req)
125
- const result = wafContext.run(data, raspRule)
125
+ const result = wafContext.run(data, raspRule, req)
126
126
 
127
127
  if (result?.keep) {
128
128
  if (limiter.isAllowed()) {
@@ -22,7 +22,7 @@ class WAFContextWrapper {
22
22
  this.cachedUserIdResults = new Map()
23
23
  }
24
24
 
25
- run ({ persistent, ephemeral }, raspRule) {
25
+ run ({ persistent, ephemeral }, raspRule, req) {
26
26
  if (this.ddwafContext.disposed) {
27
27
  log.warn('[ASM] Calling run on a disposed context')
28
28
  if (raspRule) {
@@ -141,10 +141,10 @@ class WAFContextWrapper {
141
141
  metrics.wafTimeout = result.timeout
142
142
 
143
143
  if (ruleTriggered) {
144
- Reporter.reportAttack(result)
144
+ Reporter.reportAttack(result, req)
145
145
  }
146
146
 
147
- Reporter.reportAttributes(result.attributes)
147
+ Reporter.reportAttributes(result.attributes, req)
148
148
 
149
149
  return result
150
150
  } catch (err) {
@@ -156,7 +156,7 @@ class WAFContextWrapper {
156
156
  wafRunFinished.publish({ payload })
157
157
  }
158
158
 
159
- Reporter.reportMetrics(metrics, raspRule)
159
+ Reporter.reportMetrics(metrics, raspRule, req)
160
160
  }
161
161
  }
162
162
 
@@ -234,6 +234,7 @@ class CiVisibilityExporter extends BufferingExporter {
234
234
  testManagementAttemptToFixRetries ?? this._config.testManagementAttemptToFixRetries,
235
235
  isImpactedTestsEnabled: isImpactedTestsEnabled && this._config.isImpactedTestsEnabled,
236
236
  isCoverageReportUploadEnabled,
237
+ isKeepingCoverageConfiguration: this._config.isKeepingCoverageConfiguration,
237
238
  }
238
239
  }
239
240
 
@@ -352,6 +352,7 @@ class Config {
352
352
  DD_TELEMETRY_HEARTBEAT_INTERVAL,
353
353
  DD_TELEMETRY_LOG_COLLECTION_ENABLED,
354
354
  DD_TELEMETRY_METRICS_ENABLED,
355
+ DD_TEST_TIA_KEEP_COV_CONFIG,
355
356
  DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED,
356
357
  DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED,
357
358
  DD_TRACE_AGENT_PORT,
@@ -757,6 +758,7 @@ class Config {
757
758
  unprocessedTarget['telemetry.heartbeatInterval'] = DD_TELEMETRY_HEARTBEAT_INTERVAL
758
759
  setBoolean(target, 'telemetry.logCollection', DD_TELEMETRY_LOG_COLLECTION_ENABLED)
759
760
  setBoolean(target, 'telemetry.metrics', DD_TELEMETRY_METRICS_ENABLED)
761
+ setBoolean(target, 'isKeepingCoverageConfiguration', DD_TEST_TIA_KEEP_COV_CONFIG)
760
762
  setBoolean(target, 'traceId128BitGenerationEnabled', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)
761
763
  setBoolean(target, 'traceId128BitLoggingEnabled', DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED)
762
764
  warnIfPropagationStyleConflict(
@@ -1659,6 +1661,7 @@ function getAgentUrl (url, options) {
1659
1661
  !options.port &&
1660
1662
  !getEnv('DD_AGENT_HOST') &&
1661
1663
  !getEnv('DD_TRACE_AGENT_PORT') &&
1664
+ !isTrue(getEnv('DD_CIVISIBILITY_AGENTLESS_ENABLED')) &&
1662
1665
  fs.existsSync('/var/run/datadog/apm.socket')
1663
1666
  ) {
1664
1667
  return new URL('unix:///var/run/datadog/apm.socket')
@@ -1770,6 +1770,16 @@
1770
1770
  ]
1771
1771
  }
1772
1772
  ],
1773
+ "DD_TEST_TIA_KEEP_COV_CONFIG": [
1774
+ {
1775
+ "implementation": "A",
1776
+ "type": "boolean",
1777
+ "default": "false",
1778
+ "configurationNames": [
1779
+ "isKeepingCoverageConfiguration"
1780
+ ]
1781
+ }
1782
+ ],
1773
1783
  "DD_TEST_SESSION_NAME": [
1774
1784
  {
1775
1785
  "implementation": "A",
@@ -2172,6 +2182,13 @@
2172
2182
  "default": "true"
2173
2183
  }
2174
2184
  ],
2185
+ "DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED": [
2186
+ {
2187
+ "implementation": "B",
2188
+ "type": "boolean",
2189
+ "default": "true"
2190
+ }
2191
+ ],
2175
2192
  "DD_TRACE_AZURE_EVENTHUBS_BATCH_LINKS_ENABLED": [
2176
2193
  {
2177
2194
  "implementation": "A",
@@ -17,6 +17,7 @@ module.exports = {
17
17
  SAMPLING_MECHANISM_SPAN: 8,
18
18
  SAMPLING_MECHANISM_REMOTE_USER: 11,
19
19
  SAMPLING_MECHANISM_REMOTE_DYNAMIC: 12,
20
+ SAMPLING_MECHANISM_AI_GUARD: 13,
20
21
  SPAN_SAMPLING_MECHANISM: '_dd.span_sampling.mechanism',
21
22
  SPAN_SAMPLING_RULE_RATE: '_dd.span_sampling.rule_rate',
22
23
  SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
@@ -60,6 +60,19 @@ class DataStreamsCheckpointer {
60
60
 
61
61
  return ctx
62
62
  }
63
+
64
+ /**
65
+ * Records a transaction ID at a named checkpoint without pathway propagation.
66
+ * Tags the active span (or the provided span) with the transaction ID and checkpoint name.
67
+ * @param {string} transactionId - The transaction identifier to track.
68
+ * @param {string} checkpointName - The logical checkpoint name.
69
+ * @param {object|null} [span=null] - Span to tag. Defaults to the currently active span.
70
+ */
71
+ trackTransaction (transactionId, checkpointName, span = null) {
72
+ if (!this.config.dsmEnabled) return
73
+ const activeSpan = span ?? this.tracer.scope().active()
74
+ this.dsmProcessor.trackTransaction(transactionId, checkpointName, activeSpan)
75
+ }
63
76
  }
64
77
 
65
78
  module.exports = {
@@ -71,6 +71,7 @@ const DsmPathwayCodec = lazyClass(() => require('./pathway').DsmPathwayCodec, []
71
71
  const DataStreamsCheckpointer = lazyClass(() => require('./checkpointer').DataStreamsCheckpointer, [
72
72
  'setProduceCheckpoint',
73
73
  'setConsumeCheckpoint',
74
+ 'trackTransaction',
74
75
  ])
75
76
 
76
77
  /**
@@ -79,6 +80,7 @@ const DataStreamsCheckpointer = lazyClass(() => require('./checkpointer').DataSt
79
80
  const DataStreamsManager = lazyClass(() => require('./manager').DataStreamsManager, [
80
81
  'setCheckpoint',
81
82
  'decodeDataStreamsContext',
83
+ 'trackTransaction',
82
84
  ])
83
85
 
84
86
  // TODO: Are all those methods actually public?
@@ -92,6 +94,7 @@ const DataStreamsProcessor = lazyClass(() => require('./processor').DataStreamsP
92
94
  'setCheckpoint',
93
95
  'recordOffset',
94
96
  'setOffset',
97
+ 'trackTransaction',
95
98
  'setUrl',
96
99
  'trySampleSchema',
97
100
  'canSampleSchema',
@@ -22,6 +22,15 @@ class DataStreamsManager {
22
22
  DataStreamsContext.setDataStreamsContext(ctx)
23
23
  return ctx
24
24
  }
25
+
26
+ /**
27
+ * @param {string} transactionId
28
+ * @param {string} checkpointName
29
+ * @param {object|null} [span=null]
30
+ */
31
+ trackTransaction (transactionId, checkpointName, span = null) {
32
+ this._dataStreamsProcessor.trackTransaction(transactionId, checkpointName, span)
33
+ }
25
34
  }
26
35
 
27
36
  module.exports = { DataStreamsManager }