dd-trace 5.89.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 (52) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/index.d.ts +31 -0
  3. package/package.json +8 -8
  4. package/packages/datadog-instrumentations/src/azure-durable-functions.js +75 -0
  5. package/packages/datadog-instrumentations/src/cucumber.js +40 -1
  6. package/packages/datadog-instrumentations/src/elasticsearch.js +12 -3
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/jest.js +22 -0
  9. package/packages/datadog-instrumentations/src/mocha/main.js +10 -4
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
  11. package/packages/datadog-instrumentations/src/mocha/worker.js +10 -2
  12. package/packages/datadog-instrumentations/src/playwright.js +20 -2
  13. package/packages/datadog-instrumentations/src/prisma.js +4 -2
  14. package/packages/datadog-instrumentations/src/vitest.js +16 -0
  15. package/packages/datadog-plugin-apollo/src/gateway/execute.js +8 -0
  16. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +5 -0
  17. package/packages/datadog-plugin-apollo/src/gateway/plan.js +8 -0
  18. package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +5 -0
  19. package/packages/datadog-plugin-apollo/src/gateway/request.js +4 -3
  20. package/packages/datadog-plugin-apollo/src/gateway/validate.js +4 -3
  21. package/packages/datadog-plugin-apollo/src/index.js +28 -0
  22. package/packages/datadog-plugin-azure-durable-functions/src/index.js +49 -0
  23. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +11 -1
  24. package/packages/datadog-plugin-jest/src/index.js +6 -0
  25. package/packages/datadog-plugin-playwright/src/index.js +35 -8
  26. package/packages/dd-trace/src/aiguard/noop.js +1 -1
  27. package/packages/dd-trace/src/aiguard/sdk.js +14 -5
  28. package/packages/dd-trace/src/appsec/api_security_sampler.js +22 -1
  29. package/packages/dd-trace/src/appsec/index.js +11 -1
  30. package/packages/dd-trace/src/appsec/reporter.js +28 -11
  31. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  32. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  33. package/packages/dd-trace/src/config/index.js +1 -0
  34. package/packages/dd-trace/src/config/supported-configurations.json +7 -0
  35. package/packages/dd-trace/src/constants.js +1 -0
  36. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +1 -8
  37. package/packages/dd-trace/src/encode/agentless-json.js +67 -22
  38. package/packages/dd-trace/src/exporters/agentless/index.js +58 -15
  39. package/packages/dd-trace/src/exporters/agentless/writer.js +35 -18
  40. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  41. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +9 -0
  42. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  43. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -0
  44. package/packages/dd-trace/src/plugins/apollo.js +7 -2
  45. package/packages/dd-trace/src/plugins/index.js +1 -0
  46. package/packages/dd-trace/src/plugins/util/ci.js +95 -3
  47. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +36 -2
  48. package/packages/dd-trace/src/plugins/util/web.js +31 -11
  49. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -0
  50. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +4 -0
  51. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +4 -0
  52. package/packages/dd-trace/src/standalone/product.js +2 -1
@@ -5,6 +5,14 @@ const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
5
5
  class ApolloGatewayPlanPlugin extends ApolloBasePlugin {
6
6
  static operation = 'plan'
7
7
  static prefix = 'tracing:apm:apollo:gateway:plan'
8
+
9
+ onEnd (ctx) {
10
+ const span = ctx?.currentStore?.span
11
+
12
+ if (!span) return
13
+
14
+ this.config.hooks.plan(span, ctx)
15
+ }
8
16
  }
9
17
 
10
18
  module.exports = ApolloGatewayPlanPlugin
@@ -5,6 +5,11 @@ const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
5
5
  class ApolloGatewayPostProcessingPlugin extends ApolloBasePlugin {
6
6
  static operation = 'postprocessing'
7
7
  static prefix = 'tracing:apm:apollo:gateway:postprocessing'
8
+
9
+ onAsyncStart (ctx) {
10
+ const span = ctx?.currentStore?.span
11
+ this.config.hooks.postprocessing(span, ctx)
12
+ }
8
13
  }
9
14
 
10
15
  module.exports = ApolloGatewayPostProcessingPlugin
@@ -52,15 +52,16 @@ class ApolloGatewayRequestPlugin extends ApolloBasePlugin {
52
52
  return ctx.currentStore
53
53
  }
54
54
 
55
- asyncStart (ctx) {
55
+ onAsyncStart (ctx) {
56
56
  const errors = ctx?.result?.errors
57
57
  // apollo gateway catches certain errors and returns them in the result object
58
58
  // we want to capture these errors as spans
59
59
  if (Array.isArray(errors) && errors.at(-1)?.stack && errors.at(-1).message) {
60
60
  ctx.currentStore.span.setTag('error', errors.at(-1))
61
61
  }
62
- ctx.currentStore.span.finish()
63
- return ctx.parentStore
62
+
63
+ const span = ctx?.currentStore?.span
64
+ this.config.hooks.request(span, ctx)
64
65
  }
65
66
  }
66
67
 
@@ -6,16 +6,17 @@ class ApolloGatewayValidatePlugin extends ApolloBasePlugin {
6
6
  static operation = 'validate'
7
7
  static prefix = 'tracing:apm:apollo:gateway:validate'
8
8
 
9
- end (ctx) {
9
+ onEnd (ctx) {
10
10
  const result = ctx.result
11
- const span = ctx.currentStore?.span
11
+ const span = ctx?.currentStore?.span
12
12
 
13
13
  if (!span) return
14
14
 
15
15
  if (Array.isArray(result) && result.at(-1)?.stack && result.at(-1).message) {
16
16
  span.setTag('error', result.at(-1))
17
17
  }
18
- span.finish()
18
+
19
+ this.config.hooks.validate(span, ctx)
19
20
  }
20
21
  }
21
22
 
@@ -10,6 +10,34 @@ class ApolloPlugin extends CompositePlugin {
10
10
  gateway: ApolloGatewayPlugin,
11
11
  }
12
12
  }
13
+
14
+ /**
15
+ * @override
16
+ */
17
+ configure (config) {
18
+ return super.configure(validateConfig(config))
19
+ }
20
+ }
21
+
22
+ const noop = () => {}
23
+
24
+ function validateConfig (config) {
25
+ return {
26
+ ...config,
27
+ hooks: getHooks(config),
28
+ }
29
+ }
30
+
31
+ function getHooks (config) {
32
+ const hooks = config?.hooks
33
+ const request = hooks?.request ?? noop
34
+ const validate = hooks?.validate ?? noop
35
+ const plan = hooks?.plan ?? noop
36
+ const execute = hooks?.execute ?? noop
37
+ const fetch = hooks?.fetch ?? noop
38
+ const postprocessing = hooks?.postprocessing ?? noop
39
+
40
+ return { request, validate, plan, execute, fetch, postprocessing }
13
41
  }
14
42
 
15
43
  module.exports = ApolloPlugin
@@ -0,0 +1,49 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+
5
+ class AzureDurableFunctionsPlugin extends TracingPlugin {
6
+ static get id () { return 'azure-durable-functions' }
7
+ static get operation () { return 'invoke' }
8
+ static get prefix () { return 'tracing:datadog:azure:durable-functions:invoke' }
9
+ static get type () { return 'serverless' }
10
+ static get kind () { return 'server' }
11
+
12
+ bindStart (ctx) {
13
+ const span = this.startSpan(this.operationName(), {
14
+ kind: 'internal',
15
+ type: 'serverless',
16
+
17
+ meta: {
18
+ component: 'azure-functions',
19
+ 'aas.function.name': ctx.functionName,
20
+ 'aas.function.trigger': ctx.trigger,
21
+ 'resource.name': `${ctx.trigger} ${ctx.functionName}`,
22
+ },
23
+ }, ctx)
24
+
25
+ // in the case of entity functions, operationName should be available
26
+ if (ctx.operationName) {
27
+ span.setTag('aas.function.operation', ctx.operationName)
28
+ span.setTag('resource.name', `${ctx.trigger} ${ctx.functionName} ${ctx.operationName}`
29
+ )
30
+ }
31
+
32
+ ctx.span = span
33
+ return ctx.currentStore
34
+ }
35
+
36
+ end (ctx) {
37
+ // We only want to run finish here if this is a synchronous operation
38
+ // Only synchronous operations would have `result` or `error` on `end`
39
+ // So we skip operations that dont
40
+ if (!ctx.hasOwnProperty('result') && !ctx.hasOwnProperty('error')) return
41
+ super.finish(ctx)
42
+ }
43
+
44
+ asyncStart (ctx) {
45
+ super.finish(ctx)
46
+ }
47
+ }
48
+
49
+ module.exports = AzureDurableFunctionsPlugin
@@ -247,6 +247,7 @@ class CypressPlugin {
247
247
  isSuitesSkippingEnabled = false
248
248
  isCodeCoverageEnabled = false
249
249
  isFlakyTestRetriesEnabled = false
250
+ flakyTestRetriesCount = 0
250
251
  isEarlyFlakeDetectionEnabled = false
251
252
  isKnownTestsEnabled = false
252
253
  earlyFlakeDetectionNumRetries = 0
@@ -354,7 +355,10 @@ class CypressPlugin {
354
355
  this.isKnownTestsEnabled = isKnownTestsEnabled
355
356
  if (isFlakyTestRetriesEnabled && this.isTestIsolationEnabled) {
356
357
  this.isFlakyTestRetriesEnabled = true
357
- this.cypressConfig.retries.runMode = flakyTestRetriesCount
358
+ this.flakyTestRetriesCount = flakyTestRetriesCount ?? 0
359
+ this.cypressConfig.retries.runMode = this.flakyTestRetriesCount
360
+ } else {
361
+ this.flakyTestRetriesCount = 0
358
362
  }
359
363
  this.isTestManagementTestsEnabled = isTestManagementEnabled
360
364
  this.testManagementAttemptToFixRetries = testManagementAttemptToFixRetries
@@ -1019,6 +1023,12 @@ class CypressPlugin {
1019
1023
  }
1020
1024
  }
1021
1025
  }
1026
+ // ATR: set TEST_HAS_FAILED_ALL_RETRIES when all auto test retries were exhausted and every attempt failed
1027
+ if (this.isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry &&
1028
+ this.flakyTestRetriesCount > 0 && testStatuses.length === this.flakyTestRetriesCount + 1 &&
1029
+ testStatuses.every(status => status === 'fail')) {
1030
+ this.activeTestSpan.setTag(TEST_HAS_FAILED_ALL_RETRIES, 'true')
1031
+ }
1022
1032
 
1023
1033
  // Ensure quarantined tests reported from support.js are tagged
1024
1034
  // (This catches cases where the test ran but failed, but Cypress saw it as passed)
@@ -386,6 +386,12 @@ class JestPlugin extends CiPlugin {
386
386
  return ctx.currentStore
387
387
  })
388
388
 
389
+ this.addBind('ci:jest:test-suite:hook:fn', (ctx) => {
390
+ const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(ctx.testSuiteAbsolutePath)
391
+ const store = storage('legacy').getStore()
392
+ return { ...store, span: testSuiteSpan }
393
+ })
394
+
389
395
  this.addSub('ci:jest:test:finish', ({
390
396
  span,
391
397
  status,
@@ -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 = {
@@ -184,7 +193,7 @@ class AIGuard extends NoopAIGuard {
184
193
  action = attr.action
185
194
  reason = attr.reason
186
195
  tags = attr.tags
187
- sdsFindings = attr.sds_findings
196
+ sdsFindings = attr.sds_findings || []
188
197
  blockingEnabled = attr.is_blocking_enabled ?? false
189
198
  } catch (e) {
190
199
  appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
@@ -204,9 +213,9 @@ class AIGuard extends NoopAIGuard {
204
213
  }
205
214
  if (shouldBlock) {
206
215
  span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
207
- throw new AIGuardAbortError(reason, tags)
216
+ throw new AIGuardAbortError(reason, tags, sdsFindings)
208
217
  }
209
- return { action, reason, tags }
218
+ return { action, reason, tags, sds: sdsFindings }
210
219
  })
211
220
  }
212
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
 
@@ -1661,6 +1661,7 @@ function getAgentUrl (url, options) {
1661
1661
  !options.port &&
1662
1662
  !getEnv('DD_AGENT_HOST') &&
1663
1663
  !getEnv('DD_TRACE_AGENT_PORT') &&
1664
+ !isTrue(getEnv('DD_CIVISIBILITY_AGENTLESS_ENABLED')) &&
1664
1665
  fs.existsSync('/var/run/datadog/apm.socket')
1665
1666
  ) {
1666
1667
  return new URL('unix:///var/run/datadog/apm.socket')
@@ -2182,6 +2182,13 @@
2182
2182
  "default": "true"
2183
2183
  }
2184
2184
  ],
2185
+ "DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED": [
2186
+ {
2187
+ "implementation": "B",
2188
+ "type": "boolean",
2189
+ "default": "true"
2190
+ }
2191
+ ],
2185
2192
  "DD_TRACE_AZURE_EVENTHUBS_BATCH_LINKS_ENABLED": [
2186
2193
  {
2187
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',
@@ -265,14 +265,7 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
265
265
  }
266
266
  const startTime = Date.now()
267
267
 
268
- const rawEvents = trace.map(formatSpan)
269
-
270
- const testSessionEvents = rawEvents.filter(
271
- event => event.type === 'test_session_end' || event.type === 'test_suite_end' || event.type === 'test_module_end'
272
- )
273
-
274
- const isTestSessionTrace = !!testSessionEvents.length
275
- const events = isTestSessionTrace ? testSessionEvents : rawEvents
268
+ const events = trace.map(formatSpan)
276
269
 
277
270
  this._eventCount += events.length
278
271