dd-trace 6.7.0 → 6.8.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 (147) hide show
  1. package/ci/test-optimization-validation/command-output-policy.js +5 -5
  2. package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
  3. package/ci/test-optimization-validation/generated-files.js +9 -9
  4. package/ci/test-optimization-validation/generated-test-contract.js +1 -3
  5. package/ci/test-optimization-validation/manifest-scaffold.js +1 -2
  6. package/ci/test-optimization-validation/offline-output.js +6 -6
  7. package/ci/test-optimization-validation/runner-command.js +1 -1
  8. package/ci/test-optimization-validation/safe-files.js +4 -4
  9. package/ci/test-optimization-validation/scenarios/basic-reporting.js +1 -1
  10. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  11. package/ci/vitest-no-worker-init-setup.mjs +348 -51
  12. package/index.d.ts +77 -13
  13. package/package.json +9 -3
  14. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  15. package/packages/datadog-esbuild/index.js +4 -2
  16. package/packages/datadog-esbuild/src/utils.js +1 -1
  17. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
  18. package/packages/datadog-instrumentations/src/cucumber.js +9 -6
  19. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  20. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  21. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  22. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  27. package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
  28. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  29. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  30. package/packages/datadog-instrumentations/src/jest.js +8 -14
  31. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  32. package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
  33. package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
  34. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
  35. package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
  36. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  37. package/packages/datadog-instrumentations/src/playwright.js +3 -2
  38. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  39. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
  40. package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
  41. package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
  42. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  43. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  44. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  45. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  46. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  47. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  48. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  49. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  50. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  51. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
  52. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  53. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  54. package/packages/datadog-plugin-fs/src/index.js +0 -4
  55. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  56. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  57. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  58. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  59. package/packages/datadog-plugin-http/src/client.js +2 -1
  60. package/packages/datadog-plugin-jest/src/index.js +2 -3
  61. package/packages/datadog-plugin-jest/src/util.js +2 -1
  62. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  63. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  64. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  65. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  66. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  67. package/packages/datadog-plugin-undici/src/index.js +2 -1
  68. package/packages/datadog-plugin-vitest/src/index.js +84 -11
  69. package/packages/datadog-webpack/index.js +2 -2
  70. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  71. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  72. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  73. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  74. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  75. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  76. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  77. package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
  78. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  80. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -2
  81. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  84. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  85. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  86. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  87. package/packages/dd-trace/src/config/defaults.js +14 -6
  88. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -2
  89. package/packages/dd-trace/src/config/index.js +15 -0
  90. package/packages/dd-trace/src/config/parsers.js +19 -1
  91. package/packages/dd-trace/src/config/supported-configurations.json +5 -6
  92. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  93. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  94. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  96. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  97. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  98. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  99. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  100. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  101. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  102. package/packages/dd-trace/src/flare/index.js +2 -2
  103. package/packages/dd-trace/src/id.js +1 -1
  104. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  105. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  106. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  107. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  108. package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
  109. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  110. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  111. package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
  112. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  113. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  114. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  115. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  116. package/packages/dd-trace/src/llmobs/sdk.js +4 -11
  117. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  118. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  119. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  120. package/packages/dd-trace/src/noop/proxy.js +2 -2
  121. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  122. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  123. package/packages/dd-trace/src/opentracing/span.js +1 -1
  124. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  125. package/packages/dd-trace/src/plugins/ci_plugin.js +105 -20
  126. package/packages/dd-trace/src/plugins/index.js +1 -0
  127. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  128. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  129. package/packages/dd-trace/src/plugins/util/ci.js +7 -6
  130. package/packages/dd-trace/src/plugins/util/git.js +1 -1
  131. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
  132. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  133. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  134. package/packages/dd-trace/src/plugins/util/test.js +38 -12
  135. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  136. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  137. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  138. package/packages/dd-trace/src/priority_sampler.js +6 -3
  139. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  140. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  141. package/packages/dd-trace/src/ritm.js +5 -20
  142. package/packages/dd-trace/src/startup-log.js +3 -0
  143. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  144. package/packages/dd-trace/src/util.js +16 -0
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  146. package/vendor/dist/pprof-format/index.js +1 -1
  147. package/vendor/dist/source-map/index.js +1 -1
@@ -5,6 +5,7 @@ const { storage } = require('../../datadog-core')
5
5
 
6
6
  const {
7
7
  TEST_STATUS,
8
+ TEST_TYPE,
8
9
  VITEST_POOL,
9
10
  finishAllTraceSpans,
10
11
  getTestSuitePath,
@@ -36,6 +37,10 @@ const {
36
37
  TEST_HAS_DYNAMIC_NAME,
37
38
  TEST_FINAL_STATUS,
38
39
  TEST_IS_TEST_FRAMEWORK_WORKER,
40
+ TEST_BROWSER_DRIVER,
41
+ TEST_BROWSER_NAME,
42
+ TEST_IS_RUM_ACTIVE,
43
+ TEST_PARAMETERS,
39
44
  } = require('../../dd-trace/src/plugins/util/test')
40
45
  const { COMPONENT } = require('../../dd-trace/src/constants')
41
46
  const {
@@ -50,6 +55,28 @@ const { DD_MAJOR } = require('../../../version')
50
55
  // This is because there's some loss of resolution.
51
56
  const MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION = 5
52
57
 
58
+ function setBrowserTags (tags, browserEnvironment, includeParameters) {
59
+ if (!browserEnvironment.isBrowserMode) return
60
+
61
+ tags[TEST_TYPE] = 'browser'
62
+ if (browserEnvironment.browserDriver) {
63
+ tags[TEST_BROWSER_DRIVER] = browserEnvironment.browserDriver
64
+ }
65
+ if (browserEnvironment.browserName) {
66
+ tags[TEST_BROWSER_NAME] = browserEnvironment.browserName
67
+ }
68
+ if (includeParameters && (browserEnvironment.browserName || browserEnvironment.browserProjectName)) {
69
+ const parameters = {}
70
+ if (browserEnvironment.browserName) {
71
+ parameters.browser = browserEnvironment.browserName
72
+ }
73
+ if (browserEnvironment.browserProjectName) {
74
+ parameters.project = browserEnvironment.browserProjectName
75
+ }
76
+ tags[TEST_PARAMETERS] = JSON.stringify({ arguments: parameters, metadata: {} })
77
+ }
78
+ }
79
+
53
80
  class VitestPlugin extends CiPlugin {
54
81
  static id = 'vitest'
55
82
 
@@ -100,6 +127,14 @@ class VitestPlugin extends CiPlugin {
100
127
  isModified,
101
128
  isTestFrameworkWorker,
102
129
  requestErrorTags,
130
+ isBrowserMode,
131
+ browserDriver,
132
+ browserName,
133
+ browserProjectName,
134
+ isRumActive,
135
+ testExecutionId,
136
+ testStartLine,
137
+ startTime,
103
138
  } = ctx
104
139
 
105
140
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
@@ -109,6 +144,7 @@ class VitestPlugin extends CiPlugin {
109
144
  const extraTags = {
110
145
  ...requestErrorTags,
111
146
  [TEST_SOURCE_FILE]: testSuite,
147
+ [TEST_SOURCE_START]: testStartLine || 1,
112
148
  }
113
149
  if (isRetry) {
114
150
  extraTags[TEST_IS_RETRY] = 'true'
@@ -143,12 +179,23 @@ class VitestPlugin extends CiPlugin {
143
179
  if (isTestFrameworkWorker) {
144
180
  extraTags[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
145
181
  }
182
+ if (isRumActive) {
183
+ extraTags[TEST_IS_RUM_ACTIVE] = 'true'
184
+ }
185
+ setBrowserTags(extraTags, {
186
+ browserDriver,
187
+ browserName,
188
+ browserProjectName,
189
+ isBrowserMode,
190
+ }, true)
146
191
 
147
192
  const span = this.startTestSpan(
148
193
  testName,
149
194
  testSuite,
150
195
  testSuiteSpan,
151
- extraTags
196
+ extraTags,
197
+ testExecutionId,
198
+ startTime
152
199
  )
153
200
 
154
201
  ctx.parentStore = store
@@ -297,25 +344,41 @@ class VitestPlugin extends CiPlugin {
297
344
  testSuiteAbsolutePath,
298
345
  isNew,
299
346
  isDisabled,
347
+ isRumActive,
300
348
  isTestFrameworkWorker,
301
349
  requestErrorTags,
302
350
  testSuiteSpan,
351
+ isBrowserMode,
352
+ browserDriver,
353
+ browserName,
354
+ browserProjectName,
355
+ testExecutionId,
356
+ testStartLine,
303
357
  }) => {
304
358
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
359
+ const extraTags = {
360
+ ...requestErrorTags,
361
+ [TEST_SOURCE_FILE]: testSuite,
362
+ [TEST_SOURCE_START]: testStartLine || 1,
363
+ [TEST_STATUS]: 'skip',
364
+ [TEST_FINAL_STATUS]: 'skip',
365
+ ...(isDisabled ? { [TEST_MANAGEMENT_IS_DISABLED]: 'true' } : {}),
366
+ ...(isNew ? { [TEST_IS_NEW]: 'true' } : {}),
367
+ ...(isRumActive ? { [TEST_IS_RUM_ACTIVE]: 'true' } : {}),
368
+ ...(isTestFrameworkWorker ? { [TEST_IS_TEST_FRAMEWORK_WORKER]: 'true' } : {}),
369
+ }
370
+ setBrowserTags(extraTags, {
371
+ browserDriver,
372
+ browserName,
373
+ browserProjectName,
374
+ isBrowserMode,
375
+ }, true)
305
376
  const testSpan = this.startTestSpan(
306
377
  testName,
307
378
  testSuite,
308
379
  testSuiteSpan || this.testSuiteSpan,
309
- {
310
- ...requestErrorTags,
311
- [TEST_SOURCE_FILE]: testSuite,
312
- [TEST_SOURCE_START]: 1, // we can't get the proper start line in vitest
313
- [TEST_STATUS]: 'skip',
314
- [TEST_FINAL_STATUS]: 'skip',
315
- ...(isDisabled ? { [TEST_MANAGEMENT_IS_DISABLED]: 'true' } : {}),
316
- ...(isNew ? { [TEST_IS_NEW]: 'true' } : {}),
317
- ...(isTestFrameworkWorker ? { [TEST_IS_TEST_FRAMEWORK_WORKER]: 'true' } : {}),
318
- }
380
+ extraTags,
381
+ testExecutionId
319
382
  )
320
383
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(testSpan))
321
384
  testSpan.finish()
@@ -330,6 +393,10 @@ class VitestPlugin extends CiPlugin {
330
393
  frameworkVersion,
331
394
  isTestFrameworkWorker,
332
395
  isVitestNoWorkerInitActive,
396
+ isBrowserMode,
397
+ browserDriver,
398
+ browserName,
399
+ browserProjectName,
333
400
  } = ctx
334
401
 
335
402
  const testCommand = ctx.testCommand || 'vitest run'
@@ -377,6 +444,12 @@ class VitestPlugin extends CiPlugin {
377
444
  if (isTestFrameworkWorker) {
378
445
  testSuiteMetadata[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
379
446
  }
447
+ setBrowserTags(testSuiteMetadata, {
448
+ browserDriver,
449
+ browserName,
450
+ browserProjectName,
451
+ isBrowserMode,
452
+ }, false)
380
453
 
381
454
  const codeOwners = this.getCodeOwners(testSuiteMetadata)
382
455
  if (codeOwners) {
@@ -141,7 +141,7 @@ class DatadogWebpackPlugin {
141
141
  // (#8980); absent peers stay opaque, so a build that does not opt into the feature does
142
142
  // not follow their dependency chain (#8635).
143
143
  if (matchesOptionalPeerFile(normalizedResource)) {
144
- createData.loaders = createData.loaders || []
144
+ createData.loaders ||= []
145
145
  createData.loaders.push({ loader: require.resolve('./src/optional-peer-loader') })
146
146
  log.debug('INLINE: optional-peer loader applied to %s', normalizedResource)
147
147
  return
@@ -188,7 +188,7 @@ class DatadogWebpackPlugin {
188
188
  const version = packageJson.version
189
189
  const pkgPath = request === pkg ? pkg : `${pkg}/${modulePath}`
190
190
 
191
- createData.loaders = createData.loaders || []
191
+ createData.loaders ||= []
192
192
  createData.loaders.unshift({
193
193
  loader: require.resolve('./src/loader'),
194
194
  options: { pkg, version, path: pkgPath },
@@ -307,7 +307,7 @@ function parseBody (body, contentType) {
307
307
  const formBody = Buffer.isBuffer(body) ? body.toString('utf8') : String(body)
308
308
  const params = new URLSearchParams(formBody)
309
309
  const result = {}
310
- for (const [key, value] of params.entries()) {
310
+ for (const [key, value] of params) {
311
311
  if (key in result) {
312
312
  const existing = result[key]
313
313
  if (Array.isArray(existing)) {
@@ -56,7 +56,7 @@ class HardcodedBaseAnalyzer extends Analyzer {
56
56
  }
57
57
 
58
58
  _getEvidence (value) {
59
- return { value: `${value.data}` }
59
+ return { value: value.data }
60
60
  }
61
61
 
62
62
  _getLocation (value) {
@@ -11,7 +11,7 @@ class HardcodedPasswordAnalyzer extends HardcodedBaseAnalyzer {
11
11
  }
12
12
 
13
13
  _getEvidence (value) {
14
- return { value: `${value.ident}` }
14
+ return { value: value.ident }
15
15
  }
16
16
  }
17
17
 
@@ -74,7 +74,7 @@ class SqlInjectionAnalyzer extends StoredInjectionAnalyzer {
74
74
  }
75
75
 
76
76
  analyze (value, store, dialect) {
77
- store = store || storage('legacy').getStore()
77
+ store ||= storage('legacy').getStore()
78
78
  if (!(store && store.sqlAnalyzed)) {
79
79
  super.analyze(value, store, dialect)
80
80
  }
@@ -215,9 +215,7 @@ function getTaintTrackingImpl (telemetryVerbosity, dummy = false) {
215
215
  if (dummy) return TaintTrackingNoop
216
216
 
217
217
  // with Verbosity.DEBUG every invocation of a TaintedUtils method increases the EXECUTED_PROPAGATION metric
218
- return isDebugAllowed(telemetryVerbosity)
219
- ? createImplWith(getContextDebug)
220
- : createImplWith(getContextDefault)
218
+ return createImplWith(isDebugAllowed(telemetryVerbosity) ? getContextDebug : getContextDefault)
221
219
  }
222
220
 
223
221
  function getTaintTrackingNoop () {
@@ -35,7 +35,7 @@ function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) {
35
35
  let value
36
36
  const ranges = []
37
37
  const sensitiveRanges = []
38
- objRanges = objRanges || {}
38
+ objRanges ||= {}
39
39
 
40
40
  if (objRanges || loadSensitiveRanges) {
41
41
  const cloneObj = Array.isArray(obj) ? [] : {}
@@ -21,7 +21,7 @@ let stackTraceMaxDepth
21
21
  let maxStackTraces
22
22
 
23
23
  function canAddVulnerability (vulnerability) {
24
- const hasRequiredFields = vulnerability?.evidence && vulnerability?.type && vulnerability?.location
24
+ const hasRequiredFields = vulnerability?.evidence && vulnerability.type && vulnerability.location
25
25
  if (!hasRequiredFields) return false
26
26
 
27
27
  const isDuplicated = deduplicationEnabled && isDuplicatedVulnerability(vulnerability)
@@ -64,7 +64,7 @@ function addVulnerability (iastContext, vulnerability, callSiteFrames) {
64
64
  }
65
65
 
66
66
  if (iastContext?.rootSpan) {
67
- iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
67
+ iastContext[VULNERABILITIES_KEY] ||= []
68
68
  iastContext[VULNERABILITIES_KEY].push(vulnerability)
69
69
  } else {
70
70
  sendVulnerabilities([vulnerability], span)
@@ -160,7 +160,7 @@ function flattenFields (fields, depth = 0) {
160
160
 
161
161
  if (value && typeof value === 'object') {
162
162
  const { result: flatValue, truncated: inheritTruncated } = flattenFields(value, depth + 1)
163
- truncated = truncated || inheritTruncated
163
+ truncated ||= inheritTruncated
164
164
 
165
165
  if (flatValue) {
166
166
  for (const flatKey of Object.keys(flatValue)) {
@@ -39,7 +39,7 @@ class WAFManager {
39
39
  const { obfuscatorKeyRegex, obfuscatorValueRegex } = this.config
40
40
  return new DDWAF(rules, WAFManager.defaultWafConfigPath, { obfuscatorKeyRegex, obfuscatorValueRegex })
41
41
  } catch (err) {
42
- this.ddwafVersion = this.ddwafVersion || 'unknown'
42
+ this.ddwafVersion ||= 'unknown'
43
43
  Reporter.reportWafInit(this.ddwafVersion, 'unknown')
44
44
 
45
45
  log.error('[ASM] AppSec could not load native package. In-app WAF features will not be available.', err)
@@ -303,10 +303,11 @@ function writeNewFile (filename, payload) {
303
303
  *
304
304
  * @param {string} directory directory path
305
305
  * @param {string} label directory label
306
- * @returns {{dev: number, ino: number}} stable directory identity
306
+ * @returns {{dev: bigint, ino: bigint}} stable directory identity
307
307
  */
308
308
  function captureDirectory (directory, label) {
309
- const stat = fs.lstatSync(directory)
309
+ // Windows file reference numbers exceed 2^53, where distinct directories round to one number.
310
+ const stat = fs.lstatSync(directory, { bigint: true })
310
311
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
311
312
  throw new Error(`Offline Test Optimization validation ${label} must be a regular directory.`)
312
313
  }
@@ -317,10 +318,10 @@ function captureDirectory (directory, label) {
317
318
  * Creates or validates one child directory without accepting symbolic links.
318
319
  *
319
320
  * @param {string} parent parent directory path
320
- * @param {{dev: number, ino: number}} parentIdentity expected parent identity
321
+ * @param {{dev: bigint, ino: bigint}} parentIdentity expected parent identity
321
322
  * @param {string} directory child directory path
322
323
  * @param {string} label directory label
323
- * @returns {{dev: number, ino: number}} stable child identity
324
+ * @returns {{dev: bigint, ino: bigint}} stable child identity
324
325
  */
325
326
  function createDirectory (parent, parentIdentity, directory, label) {
326
327
  assertDirectoryUnchanged(parent, parentIdentity, 'parent output')
@@ -336,7 +337,7 @@ function createDirectory (parent, parentIdentity, directory, label) {
336
337
  * Rejects a directory that changed after sink construction.
337
338
  *
338
339
  * @param {string} directory directory path
339
- * @param {{dev: number, ino: number}} identity expected directory identity
340
+ * @param {{dev: bigint, ino: bigint}} identity expected directory identity
340
341
  * @param {string} label directory label
341
342
  */
342
343
  function assertDirectoryUnchanged (directory, identity, label) {
@@ -351,7 +352,7 @@ function assertDirectoryUnchanged (directory, identity, label) {
351
352
  *
352
353
  * @param {string} filename partial payload path
353
354
  * @param {string} directory expected parent directory
354
- * @param {{dev: number, ino: number}} identity expected parent identity
355
+ * @param {{dev: bigint, ino: bigint}} identity expected parent identity
355
356
  */
356
357
  function removePartialFile (filename, directory, identity) {
357
358
  try {
@@ -15,6 +15,7 @@ const { uploadCoverageReport: uploadCoverageReportRequest } = require('../reques
15
15
  const { uploadTestScreenshot: uploadTestScreenshotRequest } = require('../requests/upload-test-screenshot')
16
16
  const { parsers } = require('../../config/parsers')
17
17
  const log = require('../../log')
18
+ const { getSegment } = require('../../util')
18
19
  const BufferingExporter = require('../../exporters/common/buffering-exporter')
19
20
  const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags')
20
21
  const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata')
@@ -28,8 +29,7 @@ function getTestConfigurationTags (tags) {
28
29
  }
29
30
  return Object.keys(tags).reduce((acc, key) => {
30
31
  if (key.startsWith('test.configuration.')) {
31
- const [, configKey] = key.split('test.configuration.')
32
- acc[configKey] = tags[key]
32
+ acc[getSegment(key, 'test.configuration.', 1)] = tags[key]
33
33
  }
34
34
  return acc
35
35
  }, {})
@@ -8,6 +8,7 @@ const FormData = require('../../../exporters/common/form-data')
8
8
  const request = require('../../../exporters/common/request')
9
9
 
10
10
  const log = require('../../../log')
11
+ const { getSegment } = require('../../../util')
11
12
  const {
12
13
  getLatestCommits,
13
14
  getRepositoryUrl,
@@ -145,7 +146,7 @@ function uploadPackFile ({ url, isEvpProxy, evpProxyPrefix, packFileToUpload, re
145
146
  try {
146
147
  const packFileContent = fs.readFileSync(packFileToUpload)
147
148
  // The original filename includes a random prefix, so we remove it here
148
- const [, filename] = path.basename(packFileToUpload).split('-')
149
+ const filename = getSegment(path.basename(packFileToUpload), '-', 1)
149
150
  form.append('packfile', packFileContent, {
150
151
  filename,
151
152
  contentType: 'application/octet-stream',
@@ -5,6 +5,7 @@ const {
5
5
  JEST_WORKER_TRACE_PAYLOAD_CODE,
6
6
  JEST_WORKER_TELEMETRY_PAYLOAD_CODE,
7
7
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
8
+ MOCHA_WORKER_LOGS_PAYLOAD_CODE,
8
9
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
9
10
  JEST_WORKER_LOGS_PAYLOAD_CODE,
10
11
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
@@ -50,6 +51,9 @@ function getInterprocessLogsCode () {
50
51
  if (getEnvironmentVariable('JEST_WORKER_ID')) {
51
52
  return JEST_WORKER_LOGS_PAYLOAD_CODE
52
53
  }
54
+ if (getEnvironmentVariable('MOCHA_WORKER_ID') === 'webdriverio') {
55
+ return MOCHA_WORKER_LOGS_PAYLOAD_CODE
56
+ }
53
57
  if (getEnvironmentVariable('TINYPOOL_WORKER_ID')) {
54
58
  return VITEST_WORKER_LOGS_PAYLOAD_CODE
55
59
  }
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const WEBDRIVERIO_WORKER_ENV = '_DD_TEST_OPTIMIZATION_WEBDRIVERIO_WORKER'
4
+ const WEBDRIVERIO_WORKER_EVENT = 'workerEvent'
5
+ const WEBDRIVERIO_WORKER_ORIGIN = 'datadog'
6
+
7
+ /**
8
+ * Wraps an internal worker payload in the event WebdriverIO reserves for worker messages.
9
+ *
10
+ * @param {unknown} args
11
+ * @returns {{args: unknown, name: string, origin: string}}
12
+ */
13
+ function createWebdriverioWorkerMessage (args) {
14
+ return {
15
+ origin: WEBDRIVERIO_WORKER_ORIGIN,
16
+ name: WEBDRIVERIO_WORKER_EVENT,
17
+ args,
18
+ }
19
+ }
20
+
21
+ module.exports = {
22
+ createWebdriverioWorkerMessage,
23
+ WEBDRIVERIO_WORKER_ENV,
24
+ WEBDRIVERIO_WORKER_EVENT,
25
+ WEBDRIVERIO_WORKER_ORIGIN,
26
+ }
@@ -2,6 +2,10 @@
2
2
  const { JSONEncoder } = require('../../encode/json-encoder')
3
3
  const { getEnvironmentVariable } = require('../../../config/helper')
4
4
  const log = require('../../../log')
5
+ const {
6
+ createWebdriverioWorkerMessage,
7
+ WEBDRIVERIO_WORKER_ENV,
8
+ } = require('./webdriverio')
5
9
 
6
10
  function getVitestWorkerPort () {
7
11
  const port = globalThis.__vitest_worker__?.ctx?.port
@@ -13,6 +17,7 @@ class Writer {
13
17
  this._encoder = new JSONEncoder()
14
18
  // Code used to identify the type of payload being sent to the main process
15
19
  this._interprocessCode = interprocessCode
20
+ this._isWebdriverioWorker = !!getEnvironmentVariable(WEBDRIVERIO_WORKER_ENV)
16
21
  }
17
22
 
18
23
  /**
@@ -43,9 +48,12 @@ class Writer {
43
48
  // Old because vitest@>=4 uses `DD_VITEST_WORKER` and reports arrays just like other frameworks
44
49
  // Before vitest@>=4, we need the `__tinypool_worker_message__` property, or tinypool will crash
45
50
  const isVitestWorkerOld = !!getEnvironmentVariable('TINYPOOL_WORKER_ID')
46
- const payload = isVitestWorkerOld
51
+ let payload = isVitestWorkerOld
47
52
  ? { __tinypool_worker_message__: true, interprocessCode: this._interprocessCode, data }
48
53
  : [this._interprocessCode, data]
54
+ if (this._isWebdriverioWorker) {
55
+ payload = createWebdriverioWorkerMessage(payload)
56
+ }
49
57
 
50
58
  const vitestWorkerPort = getVitestWorkerPort()
51
59
  if (vitestWorkerPort) {
@@ -42,7 +42,7 @@ function request (data, options, callback) {
42
42
  if (url.protocol === 'unix:') {
43
43
  opts.socketPath = url.pathname
44
44
  } else {
45
- opts.path = opts.path ?? url.path
45
+ opts.path ??= url.path
46
46
  opts.protocol = url.protocol
47
47
  opts.hostname = url.hostname
48
48
  opts.port = url.port
@@ -100,9 +100,9 @@ function request (data, options, callback) {
100
100
  if (res.statusCode === 429 && !hasRetried) {
101
101
  const resetHeader = res.headers['x-ratelimit-reset']
102
102
  const resetTs = (resetHeader === null || resetHeader === undefined)
103
- ? Number.NaN
103
+ ? NaN
104
104
  : Number.parseInt(resetHeader, 10)
105
- const waitMs = Number.isFinite(resetTs) ? Math.max(0, resetTs * 1000 - Date.now()) : Number.NaN
105
+ const waitMs = Number.isFinite(resetTs) ? Math.max(0, resetTs * 1000 - Date.now()) : NaN
106
106
 
107
107
  if (Number.isFinite(waitMs) && waitMs <= RATE_LIMIT_MAX_WAIT_MS) {
108
108
  hasRetried = true
@@ -26,7 +26,7 @@ const SETTINGS_BOOLEAN_KEYS = [
26
26
  ]
27
27
  const EARLY_FLAKE_DETECTION_KEYS = ['enabled', 'faulty_session_threshold', 'slow_test_retries']
28
28
  const TEST_MANAGEMENT_KEYS = ['attempt_to_fix_retries', 'enabled']
29
- const RETRY_THRESHOLD_PATTERN = /^\d+(?:ms|s|m|h)$/
29
+ const RETRY_THRESHOLD_PATTERN = /^\d+(?:ms|[smh])$/
30
30
  const MAX_VALIDATION_MODULES = 1000
31
31
  const MAX_VALIDATION_SUITES = 10_000
32
32
  const MAX_VALIDATION_TESTS = 100_000
@@ -32,18 +32,26 @@ const parseErrors = new Map()
32
32
  * @param {string} source - The source of the value.
33
33
  * @param {string} baseMessage - The base message to use for the warning.
34
34
  * @param {Error} [error] - An error that was thrown while parsing the value.
35
+ * @param {string} [outcome] - The outcome after the invalid value is rejected.
35
36
  */
36
- function warnInvalidValue (value, optionName, source, baseMessage, error) {
37
- const canonicalName = (optionsTable[optionName]?.canonicalName ?? optionName) + source
37
+ function warnInvalidValue (value, optionName, source, baseMessage, error, outcome = 'picked default') {
38
+ const canonicalName = optionsTable[optionName]?.canonicalName ?? optionName
39
+ const telemetryKey = canonicalName + source
40
+ const telemetryEntry = configWithOrigin.get(telemetryKey)
38
41
  // Lazy load log module to avoid circular dependency
39
- if (!parseErrors.has(canonicalName)) {
40
- // TODO: Rephrase: It will fallback to former source (or default if not set)
41
- let message = `${baseMessage}: ${util.inspect(value)} for ${optionName} (source: ${source}), picked default`
42
+ if (!parseErrors.has(telemetryKey) && !telemetryEntry?.error) {
43
+ // TODO: Report the prior source when an invalid value does not use the default.
44
+ let message = `${baseMessage}: ${util.inspect(value)} for ${optionName} (source: ${source})`
45
+ if (outcome) message += `, ${outcome}`
42
46
  if (error) {
43
47
  error.stack = error.toString()
44
48
  message += `\n\n${util.inspect(error)}`
45
49
  }
46
- parseErrors.set(canonicalName, { message })
50
+ if (telemetryEntry) {
51
+ telemetryEntry.error = { message }
52
+ } else {
53
+ parseErrors.set(telemetryKey, { message })
54
+ }
47
55
  log ??= require('../log')
48
56
  const logLevel = error ? 'error' : 'warn'
49
57
  log[logLevel](message)
@@ -434,7 +434,7 @@ export interface GeneratedConfig {
434
434
  };
435
435
  };
436
436
  featureFlags: {
437
- DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: "agentless" | "remote_config";
437
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: string;
438
438
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: string | undefined;
439
439
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: number;
440
440
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: number;
@@ -700,7 +700,7 @@ export interface GeneratedEnvVarConfig {
700
700
  DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT: boolean | undefined;
701
701
  DD_EXPERIMENTAL_TEST_REQUESTS_FS_CACHE: boolean;
702
702
  DD_EXTERNAL_ENV: string | undefined;
703
- DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: "agentless" | "remote_config";
703
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: string;
704
704
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: string | undefined;
705
705
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: number;
706
706
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: number;
@@ -343,6 +343,21 @@ class Config extends ConfigBase {
343
343
  }
344
344
  }
345
345
 
346
+ const configurationSource = this.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE
347
+ if (this.featureFlags.DD_FEATURE_FLAGS_ENABLED &&
348
+ configurationSource !== 'agentless' &&
349
+ configurationSource !== 'remote_config') {
350
+ warnInvalidValue(
351
+ configurationSource,
352
+ 'DD_FEATURE_FLAGS_CONFIGURATION_SOURCE',
353
+ this.getOrigin('featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE'),
354
+ 'Unsupported Feature Flagging configuration source',
355
+ undefined,
356
+ 'provider disabled'
357
+ )
358
+ setAndTrack(this, 'featureFlags.DD_FEATURE_FLAGS_ENABLED', false)
359
+ }
360
+
346
361
  if (this.url ||
347
362
  os.type() !== 'Windows_NT' &&
348
363
  !trackedConfigOrigins.has('hostname') &&
@@ -58,6 +58,17 @@ const transformers = {
58
58
  toLowerCase (value) {
59
59
  return toCase(value, 'toLowerCase')
60
60
  },
61
+ /**
62
+ * Normalizes a Feature Flagging configuration source.
63
+ * A blank value is treated as unset so legacy configuration can still apply.
64
+ *
65
+ * @param {string} value
66
+ * @returns {string | undefined}
67
+ */
68
+ configurationSource (value) {
69
+ const normalized = value.trim().toLowerCase()
70
+ return normalized || undefined
71
+ },
61
72
  toUpperCase (value) {
62
73
  return toCase(value, 'toUpperCase')
63
74
  },
@@ -161,7 +172,14 @@ const transformers = {
161
172
  return transformers.stripColonWhitespace(item)
162
173
  })
163
174
  }
164
- return value.replaceAll(/\s*:\s*/g, ':')
175
+ // Only whitespace adjacent to a colon is removed; the outer whitespace of the first and
176
+ // last segment stays.
177
+ const parts = value.split(':')
178
+ for (let i = 0; i < parts.length; i++) {
179
+ if (i !== 0) parts[i] = parts[i].trimStart()
180
+ if (i !== parts.length - 1) parts[i] = parts[i].trimEnd()
181
+ }
182
+ return parts.join(':')
165
183
  },
166
184
  /**
167
185
  * @param {string} value
@@ -849,10 +849,9 @@
849
849
  "implementation": "A",
850
850
  "type": "string",
851
851
  "default": "agentless",
852
- "allowed": "agentless|remote_config",
853
- "description": "Experimental: Select where Feature Flagging loads Universal Flag Configuration. Supported values are agentless and remote_config.",
852
+ "description": "Select where Feature Flagging loads Universal Flag Configuration. Supported values are agentless and remote_config; offline is reserved and currently unsupported.",
854
853
  "namespace": "featureFlags",
855
- "transform": "toLowerCase"
854
+ "transform": "configurationSource"
856
855
  }
857
856
  ],
858
857
  "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL": [
@@ -860,7 +859,7 @@
860
859
  "implementation": "A",
861
860
  "type": "string",
862
861
  "default": null,
863
- "description": "Experimental: Override the agentless Feature Flagging UFC endpoint or base URL.",
862
+ "description": "Override the agentless Feature Flagging UFC endpoint or base URL.",
864
863
  "namespace": "featureFlags",
865
864
  "sensitive": true
866
865
  }
@@ -870,7 +869,7 @@
870
869
  "implementation": "A",
871
870
  "type": "int",
872
871
  "default": "30",
873
- "description": "Experimental: Set the agentless Feature Flagging UFC polling interval in seconds, capped at one hour.",
872
+ "description": "Set the agentless Feature Flagging UFC polling interval in seconds, capped at one hour.",
874
873
  "allowed": "[1-9]\\d*",
875
874
  "namespace": "featureFlags"
876
875
  }
@@ -880,7 +879,7 @@
880
879
  "implementation": "A",
881
880
  "type": "int",
882
881
  "default": "5",
883
- "description": "Experimental: Set the agentless Feature Flagging UFC request timeout in seconds.",
882
+ "description": "Set the agentless Feature Flagging UFC request timeout in seconds.",
884
883
  "allowed": "[1-9]\\d*",
885
884
  "namespace": "featureFlags"
886
885
  }
@@ -40,7 +40,7 @@ function encodeVarintInto (target, offset, value) {
40
40
  let i = offset
41
41
  const limit = offset + maxVarLen64 - 1
42
42
  // if first byte is 1, the number is negative in javascript, but we want to interpret it as positive
43
- while ((high !== 0 || low < 0 || low > 0x80) && i < limit) {
43
+ while ((high !== 0 || low < 0 || low > 0x7F) && i < limit) {
44
44
  target[i] = (low & 0x7F) | 0x80
45
45
  low >>>= 7
46
46
  low |= (high & 0x7F) << 25
@@ -88,7 +88,7 @@ function decodeUvarint64 (
88
88
  low |= n << s
89
89
  }
90
90
  if (s > 0) {
91
- high |= s - 32 > 0 ? n << (s - 32) : n >> (32 - s)
91
+ high |= s > 32 ? n << (s - 32) : n >> (32 - s)
92
92
  }
93
93
  return [low, high, bytes]
94
94
  }
@@ -96,8 +96,7 @@ function decodeUvarint64 (
96
96
  low |= (n & 0x7F) << s
97
97
  }
98
98
  if (s > 0) {
99
- high |=
100
- s - 32 > 0 ? (n & 0x7F) << (s - 32) : (n & 0x7F) >> (32 - s)
99
+ high |= s > 32 ? (n & 0x7F) << (s - 32) : (n & 0x7F) >> (32 - s)
101
100
  }
102
101
  s += 7
103
102
  }