dd-trace 5.87.0 → 5.89.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 (119) hide show
  1. package/LICENSE-3rdparty.csv +60 -32
  2. package/ext/exporters.d.ts +1 -0
  3. package/ext/exporters.js +1 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +234 -4
  6. package/package.json +18 -11
  7. package/packages/datadog-instrumentations/src/ai.js +54 -90
  8. package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +27 -110
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/compiler.js +74 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/index.js +43 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/matcher.js +49 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/transformer.js +121 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/{transforms.js → orchestrion/transforms.js} +143 -17
  18. package/packages/datadog-instrumentations/src/jest.js +176 -54
  19. package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
  20. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  21. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
  22. package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
  23. package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
  24. package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
  25. package/packages/datadog-plugin-cucumber/src/index.js +9 -6
  26. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +62 -5
  27. package/packages/datadog-plugin-cypress/src/source-map-utils.js +297 -0
  28. package/packages/datadog-plugin-cypress/src/support.js +52 -9
  29. package/packages/datadog-plugin-jest/src/index.js +12 -2
  30. package/packages/datadog-plugin-jest/src/util.js +2 -1
  31. package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
  32. package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
  33. package/packages/datadog-plugin-mocha/src/index.js +9 -6
  34. package/packages/datadog-plugin-playwright/src/index.js +10 -6
  35. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  36. package/packages/dd-trace/src/aiguard/sdk.js +5 -1
  37. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
  39. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
  40. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
  41. package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
  42. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +3 -2
  45. package/packages/dd-trace/src/azure_metadata.js +0 -2
  46. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  47. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +3 -0
  48. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
  49. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  50. package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
  51. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  52. package/packages/dd-trace/src/config/defaults.js +148 -197
  53. package/packages/dd-trace/src/config/helper.js +43 -1
  54. package/packages/dd-trace/src/config/index.js +38 -14
  55. package/packages/dd-trace/src/config/supported-configurations.json +4125 -512
  56. package/packages/dd-trace/src/constants.js +0 -2
  57. package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
  58. package/packages/dd-trace/src/datastreams/checkpointer.js +13 -0
  59. package/packages/dd-trace/src/datastreams/index.js +3 -0
  60. package/packages/dd-trace/src/datastreams/manager.js +9 -0
  61. package/packages/dd-trace/src/datastreams/pathway.js +22 -3
  62. package/packages/dd-trace/src/datastreams/processor.js +140 -4
  63. package/packages/dd-trace/src/encode/agentless-json.js +155 -0
  64. package/packages/dd-trace/src/exporter.js +2 -0
  65. package/packages/dd-trace/src/exporters/agent/writer.js +21 -8
  66. package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
  67. package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
  68. package/packages/dd-trace/src/exporters/common/request.js +4 -4
  69. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
  70. package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
  71. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
  72. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
  73. package/packages/dd-trace/src/opentracing/span.js +6 -4
  74. package/packages/dd-trace/src/pkg.js +1 -1
  75. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
  76. package/packages/dd-trace/src/plugins/database.js +15 -2
  77. package/packages/dd-trace/src/plugins/util/test.js +48 -0
  78. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
  79. package/packages/dd-trace/src/propagation-hash/index.js +145 -0
  80. package/packages/dd-trace/src/proxy.js +6 -1
  81. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  82. package/packages/dd-trace/src/startup-log.js +53 -19
  83. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  84. package/vendor/dist/@datadog/source-map/index.js +1 -1
  85. package/vendor/dist/@isaacs/ttlcache/index.js +1 -1
  86. package/vendor/dist/@opentelemetry/core/index.js +1 -1
  87. package/vendor/dist/@opentelemetry/resources/index.js +1 -1
  88. package/vendor/dist/astring/index.js +1 -1
  89. package/vendor/dist/crypto-randomuuid/index.js +1 -1
  90. package/vendor/dist/escape-string-regexp/index.js +1 -1
  91. package/vendor/dist/esquery/index.js +1 -1
  92. package/vendor/dist/ignore/index.js +1 -1
  93. package/vendor/dist/istanbul-lib-coverage/index.js +1 -1
  94. package/vendor/dist/jest-docblock/index.js +1 -1
  95. package/vendor/dist/jsonpath-plus/index.js +1 -1
  96. package/vendor/dist/limiter/index.js +1 -1
  97. package/vendor/dist/lodash.sortby/index.js +1 -1
  98. package/vendor/dist/lru-cache/index.js +1 -1
  99. package/vendor/dist/meriyah/index.js +1 -1
  100. package/vendor/dist/module-details-from-path/index.js +1 -1
  101. package/vendor/dist/mutexify/promise/index.js +1 -1
  102. package/vendor/dist/opentracing/index.js +1 -1
  103. package/vendor/dist/path-to-regexp/index.js +1 -1
  104. package/vendor/dist/pprof-format/index.js +1 -1
  105. package/vendor/dist/protobufjs/index.js +1 -1
  106. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  107. package/vendor/dist/retry/index.js +1 -1
  108. package/vendor/dist/rfdc/index.js +1 -1
  109. package/vendor/dist/semifies/index.js +1 -1
  110. package/vendor/dist/shell-quote/index.js +1 -1
  111. package/vendor/dist/source-map/index.js +1 -1
  112. package/vendor/dist/source-map/lib/util/index.js +1 -1
  113. package/vendor/dist/tlhunter-sorted-set/index.js +1 -1
  114. package/vendor/dist/ttl-set/index.js +1 -1
  115. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +0 -33
  116. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
  117. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
  118. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
  119. package/packages/dd-trace/src/scope/noop/scope.js +0 -21
@@ -1,5 +1,8 @@
1
1
  'use strict'
2
2
 
3
+ const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
4
+ let rumFlushWaitMillis = 500
5
+
3
6
  let isEarlyFlakeDetectionEnabled = false
4
7
  let isKnownTestsEnabled = false
5
8
  let knownTestsForSuite = []
@@ -15,9 +18,10 @@ const retryReasonsByTestName = new Map()
15
18
  // Track quarantined test errors - we catch them in Cypress.on('fail') but need to report to Datadog
16
19
  const quarantinedTestErrors = new Map()
17
20
 
18
- // We need to grab the original window as soon as possible,
19
- // in case the test changes the origin. If the test does change the origin,
20
- // any call to `cy.window()` will result in a cross origin error.
21
+ // Track the most recently loaded window in the AUT. Updated via the 'window:load'
22
+ // event so we always get the real app window (after cy.visit()), not the
23
+ // about:blank window that exists when beforeEach runs. If the test later navigates
24
+ // to a cross-origin URL, safeGetRum() handles the access error.
21
25
  let originalWindow
22
26
 
23
27
  // If the test is using multi domain with cy.origin, trying to access
@@ -165,18 +169,45 @@ beforeEach(function () {
165
169
  retryReasonsByTestName.delete(testName)
166
170
  }
167
171
 
172
+ cy.on('window:load', (win) => {
173
+ originalWindow = win
174
+ })
175
+
168
176
  cy.task('dd:beforeEach', {
169
177
  testName,
170
178
  testSuite: Cypress.mocha.getRootSuite().file,
171
179
  }).then(({ traceId, shouldSkip }) => {
172
- Cypress.env('traceId', traceId)
180
+ if (traceId) {
181
+ cy.setCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME, traceId).then(() => {
182
+ // When testIsolation:false, the page is not reset between tests, so the RUM session
183
+ // stopped in afterEach must be explicitly restarted so events in this test are
184
+ // associated with the new testExecutionId.
185
+ //
186
+ // After stopSession(), the RUM SDK creates a new session upon a user interaction
187
+ // (click, scroll, keydown, or touchstart). We dispatch a synthetic click on the window
188
+ // to trigger session renewal, then call startView() to establish a view boundary.
189
+ if (!isTestIsolationEnabled && originalWindow) {
190
+ const rum = safeGetRum(originalWindow)
191
+ if (rum) {
192
+ try {
193
+ const evt = new originalWindow.MouseEvent('click', { bubbles: true, cancelable: true })
194
+ // The browser-sdk addEventListener wrapper filters out untrusted synthetic events
195
+ // unless __ddIsTrusted is set. Set it so the click triggers expandOrRenewSession().
196
+ // See: https://github.com/DataDog/browser-sdk/blob/v6.27.1/packages/core/src/browser/addEventListener.ts#L119
197
+ Object.defineProperty(evt, '__ddIsTrusted', { value: true })
198
+ originalWindow.dispatchEvent(evt)
199
+ } catch {}
200
+ if (rum.startView) {
201
+ rum.startView()
202
+ }
203
+ }
204
+ }
205
+ })
206
+ }
173
207
  if (shouldSkip) {
174
208
  this.skip()
175
209
  }
176
210
  })
177
- cy.window().then(win => {
178
- originalWindow = win
179
- })
180
211
  })
181
212
 
182
213
  before(function () {
@@ -195,6 +226,9 @@ before(function () {
195
226
  isImpactedTestsEnabled = suiteConfig.isImpactedTestsEnabled
196
227
  isModifiedTest = suiteConfig.isModifiedTest
197
228
  isTestIsolationEnabled = suiteConfig.isTestIsolationEnabled
229
+ if (Number.isFinite(suiteConfig.rumFlushWaitMillis)) {
230
+ rumFlushWaitMillis = suiteConfig.rumFlushWaitMillis
231
+ }
198
232
  }
199
233
  })
200
234
  })
@@ -224,6 +258,7 @@ afterEach(function () {
224
258
 
225
259
  const testInfo = {
226
260
  testName,
261
+ testItTitle: currentTest.title,
227
262
  testSuite: Cypress.mocha.getRootSuite().file,
228
263
  testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute,
229
264
  // For quarantined tests, report the actual state (failed) to Datadog, not what Cypress thinks (passed)
@@ -238,11 +273,19 @@ afterEach(function () {
238
273
  isQuarantined: isQuarantinedTestThatFailed,
239
274
  }
240
275
  try {
241
- testInfo.testSourceLine = Cypress.mocha.getRunner().currentRunnable.invocationDetails.line
276
+ const invocationDetails = Cypress.mocha.getRunner().currentRunnable.invocationDetails
277
+ testInfo.testSourceLine = invocationDetails.line
278
+ testInfo.testSourceStack = invocationDetails.stack
242
279
  } catch {}
243
280
 
244
- if (safeGetRum(originalWindow)) {
281
+ const rum = safeGetRum(originalWindow)
282
+ if (rum) {
245
283
  testInfo.isRUMActive = true
284
+ if (rum.stopSession) {
285
+ rum.stopSession()
286
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
287
+ cy.wait(rumFlushWaitMillis)
288
+ }
246
289
  }
247
290
  let coverage
248
291
  try {
@@ -182,9 +182,10 @@ class JestPlugin extends CiPlugin {
182
182
  config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
183
183
  config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
184
184
  config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
185
+ config._ddRequestErrorTags = this.getSessionRequestErrorTags()
185
186
  config._ddItrCorrelationId = this.itrCorrelationId
186
187
  config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
187
- config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
188
+ config._ddEarlyFlakeDetectionSlowTestRetries = this.libraryConfig?.earlyFlakeDetectionSlowTestRetries ?? {}
188
189
  config._ddRepositoryRoot = this.repositoryRoot
189
190
  config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
190
191
  config._ddIsTestManagementTestsEnabled = this.libraryConfig?.isTestManagementEnabled ?? false
@@ -208,6 +209,7 @@ class JestPlugin extends CiPlugin {
208
209
  _ddTestSessionId: testSessionId,
209
210
  _ddTestCommand: testCommand,
210
211
  _ddTestModuleId: testModuleId,
212
+ _ddRequestErrorTags: requestErrorTags,
211
213
  _ddItrCorrelationId: itrCorrelationId,
212
214
  _ddForcedToRun,
213
215
  _ddUnskippable,
@@ -219,7 +221,11 @@ class JestPlugin extends CiPlugin {
219
221
  'x-datadog-parent-id': testModuleId,
220
222
  })
221
223
 
222
- const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
224
+ const testSuiteMetadata = {
225
+ ...getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest'),
226
+ // requestErrorTags from test env options may be undefined
227
+ ...(requestErrorTags !== undefined && requestErrorTags !== null ? requestErrorTags : {}),
228
+ }
223
229
 
224
230
  if (_ddUnskippable) {
225
231
  const unskippableSuites = this.getUnskippableSuites(_ddUnskippable)
@@ -389,6 +395,7 @@ class JestPlugin extends CiPlugin {
389
395
  attemptToFixFailed,
390
396
  isAtrRetry,
391
397
  finalStatus,
398
+ earlyFlakeAbortReason,
392
399
  }) => {
393
400
  span.setTag(TEST_STATUS, status)
394
401
  if (finalStatus) {
@@ -409,6 +416,9 @@ class JestPlugin extends CiPlugin {
409
416
  span.setTag(TEST_IS_RETRY, 'true')
410
417
  span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
411
418
  }
419
+ if (earlyFlakeAbortReason) {
420
+ span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
421
+ }
412
422
 
413
423
  this.telemetry.ciVisEvent(
414
424
  TELEMETRY_EVENT_FINISHED,
@@ -3,7 +3,7 @@
3
3
  const { readFileSync } = require('fs')
4
4
  const { parse } = require('../../../vendor/dist/jest-docblock')
5
5
 
6
- const { getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
6
+ const { getTestSuitePath, getEfdRetryCount } = require('../../dd-trace/src/plugins/util/test')
7
7
  const log = require('../../dd-trace/src/log')
8
8
 
9
9
  /**
@@ -172,4 +172,5 @@ module.exports = {
172
172
  getJestTestName,
173
173
  getJestSuitesToRun,
174
174
  isMarkedAsUnskippable,
175
+ getEfdRetryCount,
175
176
  }
@@ -40,14 +40,18 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
40
40
  * @returns {ConsumerBacklog}
41
41
  */
42
42
  transformCommit (commit) {
43
- const { groupId, partition, offset, topic } = commit
44
- return {
43
+ const { groupId, partition, offset, topic, clusterId } = commit
44
+ const backlog = {
45
45
  partition,
46
46
  topic,
47
47
  type: 'kafka_commit',
48
48
  offset: Number(offset),
49
49
  consumer_group: groupId,
50
50
  }
51
+ if (clusterId) {
52
+ backlog.kafka_cluster_id = clusterId
53
+ }
54
+ return backlog
51
55
  }
52
56
 
53
57
  commit (commitList) {
@@ -65,6 +69,22 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
65
69
  }
66
70
  }
67
71
 
72
+ start (ctx) {
73
+ if (!this.config.dsmEnabled) return
74
+ const { topic, message, groupId, clusterId } = ctx.extractedArgs || ctx
75
+ const headers = convertToTextMap(message?.headers)
76
+ if (!headers) return
77
+
78
+ const { span } = ctx.currentStore
79
+ const payloadSize = getMessageSize(message)
80
+ this.tracer.decodeDataStreamsContext(headers)
81
+ const edgeTags = ['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka']
82
+ if (clusterId) {
83
+ edgeTags.push(`kafka_cluster_id:${clusterId}`)
84
+ }
85
+ this.tracer.setCheckpoint(edgeTags, span, payloadSize)
86
+ }
87
+
68
88
  bindStart (ctx) {
69
89
  const { topic, partition, message, groupId, clusterId } = ctx.extractedArgs || ctx
70
90
 
@@ -89,16 +109,6 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
89
109
  }, ctx)
90
110
  if (message?.offset) span.setTag('kafka.message.offset', message?.offset)
91
111
 
92
- if (this.config.dsmEnabled && headers) {
93
- const payloadSize = getMessageSize(message)
94
- this.tracer.decodeDataStreamsContext(headers)
95
- const edgeTags = ['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka']
96
- if (clusterId) {
97
- edgeTags.push(`kafka_cluster_id:${clusterId}`)
98
- }
99
- this.tracer.setCheckpoint(edgeTags, span, payloadSize)
100
- }
101
-
102
112
  if (afterStartCh.hasSubscribers) {
103
113
  afterStartCh.publish({ topic, partition, message, groupId, currentStore: ctx.currentStore })
104
114
  }
@@ -37,16 +37,20 @@ class KafkajsProducerPlugin extends ProducerPlugin {
37
37
  * @param {ProducerResponseItem} response
38
38
  * @returns {ProducerBacklog}
39
39
  */
40
- transformProduceResponse (response) {
40
+ transformProduceResponse (response, clusterId) {
41
41
  // In produce protocol >=v3, the offset key changes from `offset` to `baseOffset`
42
42
  const { topicName: topic, partition, offset, baseOffset } = response
43
43
  const offsetAsLong = offset || baseOffset
44
- return {
44
+ const backlog = {
45
45
  type: 'kafka_produce',
46
46
  partition,
47
47
  offset: offsetAsLong ? Number(offsetAsLong) : undefined,
48
48
  topic,
49
49
  }
50
+ if (clusterId) {
51
+ backlog.kafka_cluster_id = clusterId
52
+ }
53
+ return backlog
50
54
  }
51
55
 
52
56
  /**
@@ -56,6 +60,7 @@ class KafkajsProducerPlugin extends ProducerPlugin {
56
60
  */
57
61
  commit (ctx) {
58
62
  const commitList = ctx.result
63
+ const clusterId = ctx.clusterId
59
64
 
60
65
  if (!this.config.dsmEnabled) return
61
66
  if (!commitList || !Array.isArray(commitList)) return
@@ -65,12 +70,33 @@ class KafkajsProducerPlugin extends ProducerPlugin {
65
70
  'offset',
66
71
  'topic',
67
72
  ]
68
- for (const commit of commitList.map(this.transformProduceResponse)) {
73
+ for (const commit of commitList.map(r => this.transformProduceResponse(r, clusterId))) {
69
74
  if (keys.some(key => !commit.hasOwnProperty(key))) continue
70
75
  this.tracer.setOffset(commit)
71
76
  }
72
77
  }
73
78
 
79
+ start (ctx) {
80
+ if (!this.config.dsmEnabled) return
81
+ const { topic, messages, clusterId, disableHeaderInjection, currentStore: { span } } = ctx
82
+
83
+ for (const message of messages) {
84
+ if (message !== null && typeof message === 'object') {
85
+ const payloadSize = getMessageSize(message)
86
+ const edgeTags = ['direction:out', `topic:${topic}`, 'type:kafka']
87
+
88
+ if (clusterId) {
89
+ edgeTags.push(`kafka_cluster_id:${clusterId}`)
90
+ }
91
+
92
+ const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
93
+ if (!disableHeaderInjection) {
94
+ DsmPathwayCodec.encode(dataStreamsContext, message.headers)
95
+ }
96
+ }
97
+ }
98
+ }
99
+
74
100
  bindStart (ctx) {
75
101
  const { topic, messages, bootstrapServers, clusterId, disableHeaderInjection } = ctx
76
102
  const span = this.startSpan({
@@ -89,25 +115,10 @@ class KafkajsProducerPlugin extends ProducerPlugin {
89
115
  span.setTag(BOOTSTRAP_SERVERS_KEY, bootstrapServers)
90
116
  }
91
117
  for (const message of messages) {
92
- if (message !== null && typeof message === 'object') {
93
- // message headers are not supported for kafka broker versions <0.11
94
- if (!disableHeaderInjection) {
95
- message.headers ??= {}
96
- this.tracer.inject(span, 'text_map', message.headers)
97
- }
98
- if (this.config.dsmEnabled) {
99
- const payloadSize = getMessageSize(message)
100
- const edgeTags = ['direction:out', `topic:${topic}`, 'type:kafka']
101
-
102
- if (clusterId) {
103
- edgeTags.push(`kafka_cluster_id:${clusterId}`)
104
- }
105
-
106
- const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
107
- if (!disableHeaderInjection) {
108
- DsmPathwayCodec.encode(dataStreamsContext, message.headers)
109
- }
110
- }
118
+ // message headers are not supported for kafka broker versions <0.11
119
+ if (message !== null && typeof message === 'object' && !disableHeaderInjection) {
120
+ message.headers ??= {}
121
+ this.tracer.inject(span, 'text_map', message.headers)
111
122
  }
112
123
  }
113
124
 
@@ -93,12 +93,15 @@ class MochaPlugin extends CiPlugin {
93
93
  return
94
94
  }
95
95
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
96
- const testSuiteMetadata = getTestSuiteCommonTags(
97
- this.command,
98
- this.frameworkVersion,
99
- testSuite,
100
- 'mocha'
101
- )
96
+ const testSuiteMetadata = {
97
+ ...getTestSuiteCommonTags(
98
+ this.command,
99
+ this.frameworkVersion,
100
+ testSuite,
101
+ 'mocha'
102
+ ),
103
+ ...this.getSessionRequestErrorTags(),
104
+ }
102
105
  if (isUnskippable) {
103
106
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
104
107
  this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
@@ -120,12 +120,15 @@ class PlaywrightPlugin extends CiPlugin {
120
120
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
121
121
  const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
122
122
 
123
- const testSuiteMetadata = getTestSuiteCommonTags(
124
- this.command,
125
- this.frameworkVersion,
126
- testSuite,
127
- 'playwright'
128
- )
123
+ const testSuiteMetadata = {
124
+ ...getTestSuiteCommonTags(
125
+ this.command,
126
+ this.frameworkVersion,
127
+ testSuite,
128
+ 'playwright'
129
+ ),
130
+ ...this.getSessionRequestErrorTags(),
131
+ }
129
132
  if (testSourceFile) {
130
133
  testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
131
134
  testSuiteMetadata[TEST_SOURCE_START] = 1
@@ -222,6 +225,7 @@ class PlaywrightPlugin extends CiPlugin {
222
225
  // for a test session. They can be passed the same way `DD_PLAYWRIGHT_WORKER` is passed.
223
226
  formattedSpan.meta[TEST_SESSION_ID] = this.testSessionSpan.context().toTraceId()
224
227
  formattedSpan.meta[TEST_MODULE_ID] = this.testModuleSpan.context().toSpanId()
228
+ Object.assign(formattedSpan.meta, this.getSessionRequestErrorTags())
225
229
  formattedSpan.meta[TEST_COMMAND] = this.command
226
230
  formattedSpan.meta[TEST_MODULE] = this.constructor.id
227
231
  // MISSING _trace.startTime and _trace.ticks - because by now the suite is already serialized
@@ -275,9 +275,11 @@ class VitestPlugin extends CiPlugin {
275
275
  this.addBind('ci:vitest:test-suite:start', (ctx) => {
276
276
  const { testSuiteAbsolutePath, frameworkVersion } = ctx
277
277
 
278
+ // TODO: Handle case where the command is not set
278
279
  this.command = getValueFromEnvSources('DD_CIVISIBILITY_TEST_COMMAND')
279
280
  this.frameworkVersion = frameworkVersion
280
281
  const testSessionSpanContext = this.tracer.extract('text_map', {
282
+ // TODO: Handle case where the session ID or module ID is not set
281
283
  'x-datadog-trace-id': getValueFromEnvSources('DD_CIVISIBILITY_TEST_SESSION_ID'),
282
284
  'x-datadog-parent-id': getValueFromEnvSources('DD_CIVISIBILITY_TEST_MODULE_ID'),
283
285
  })
@@ -301,14 +303,17 @@ class VitestPlugin extends CiPlugin {
301
303
  }
302
304
 
303
305
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
304
- const testSuiteMetadata = getTestSuiteCommonTags(
305
- this.command,
306
- this.frameworkVersion,
307
- testSuite,
308
- 'vitest'
309
- )
310
- testSuiteMetadata[TEST_SOURCE_FILE] = testSuite
311
- testSuiteMetadata[TEST_SOURCE_START] = 1
306
+ // Request error tags are applied to test spans in the main process (worker-report:trace handler)
307
+ const testSuiteMetadata = {
308
+ ...getTestSuiteCommonTags(
309
+ this.command,
310
+ this.frameworkVersion,
311
+ testSuite,
312
+ 'vitest'
313
+ ),
314
+ [TEST_SOURCE_FILE]: testSuite,
315
+ [TEST_SOURCE_START]: 1,
316
+ }
312
317
 
313
318
  const codeOwners = this.getCodeOwners(testSuiteMetadata)
314
319
  if (codeOwners) {
@@ -175,7 +175,7 @@ class AIGuard extends NoopAIGuard {
175
175
  `AI Guard service call failed, status ${response.status}`,
176
176
  { errors: response.body?.errors })
177
177
  }
178
- let action, reason, tags, blockingEnabled
178
+ let action, reason, tags, sdsFindings, blockingEnabled
179
179
  try {
180
180
  const attr = response.body.data.attributes
181
181
  if (!attr.action) {
@@ -184,6 +184,7 @@ class AIGuard extends NoopAIGuard {
184
184
  action = attr.action
185
185
  reason = attr.reason
186
186
  tags = attr.tags
187
+ sdsFindings = attr.sds_findings
187
188
  blockingEnabled = attr.is_blocking_enabled ?? false
188
189
  } catch (e) {
189
190
  appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
@@ -198,6 +199,9 @@ class AIGuard extends NoopAIGuard {
198
199
  if (tags?.length > 0) {
199
200
  metaStruct.attack_categories = tags
200
201
  }
202
+ if (sdsFindings?.length > 0) {
203
+ metaStruct.sds = sdsFindings
204
+ }
201
205
  if (shouldBlock) {
202
206
  span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
203
207
  throw new AIGuardAbortError(reason, tags)
@@ -39,7 +39,7 @@ class CookieAnalyzer extends Analyzer {
39
39
  }
40
40
 
41
41
  _checkOCE (context, value) {
42
- if (value && value.location) {
42
+ if (value?.location) {
43
43
  return true
44
44
  }
45
45
  return super._checkOCE(context, value)
@@ -12,7 +12,7 @@ class SSRFAnalyzer extends InjectionAnalyzer {
12
12
  this.addSub('apm:http:client:request:start', ({ args }) => {
13
13
  if (typeof args.originalUrl === 'string') {
14
14
  this.analyze(args.originalUrl)
15
- } else if (args.options && args.options.host) {
15
+ } else if (args.options?.host) {
16
16
  this.analyze(args.options.host)
17
17
  }
18
18
  })
@@ -36,7 +36,7 @@ class UnvalidatedRedirectAnalyzer extends InjectionAnalyzer {
36
36
  }
37
37
 
38
38
  isLocationHeader (name) {
39
- return name && name.trim().toLowerCase() === 'location'
39
+ return name?.trim().toLowerCase() === 'location'
40
40
  }
41
41
 
42
42
  _isVulnerable (value, iastContext) {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { storage } = require('../../../../../datadog-core')
4
- const { getNonDDCallSiteFrames } = require('../path-line')
4
+ const { getCallSiteFramesForLocation } = require('../path-line')
5
5
  const { getIastContext, getIastStackTraceId } = require('../iast-context')
6
6
  const overheadController = require('../overhead-controller')
7
7
  const { SinkIastPlugin } = require('../iast-plugin')
@@ -35,9 +35,8 @@ class Analyzer extends SinkIastPlugin {
35
35
 
36
36
  _reportEvidence (value, context, evidence) {
37
37
  const callSiteFrames = getVulnerabilityCallSiteFrames()
38
- const nonDDCallSiteFrames = getNonDDCallSiteFrames(callSiteFrames, this._getExcludedPaths())
39
-
40
- const location = this._getLocation(value, nonDDCallSiteFrames)
38
+ const frames = getCallSiteFramesForLocation(callSiteFrames, this._getExcludedPaths())
39
+ const location = this._getLocation(value, frames)
41
40
 
42
41
  if (!this._isExcluded(location)) {
43
42
  const originalLocation = this._getOriginalLocation(location)
@@ -51,7 +50,7 @@ class Analyzer extends SinkIastPlugin {
51
50
  stackId
52
51
  )
53
52
 
54
- addVulnerability(context, vulnerability, nonDDCallSiteFrames)
53
+ addVulnerability(context, vulnerability, frames)
55
54
  }
56
55
  }
57
56
 
@@ -8,29 +8,40 @@ const { getOriginalPathAndLineFromSourceMap } = require('./taint-tracking/rewrit
8
8
  const pathLine = {
9
9
  getNodeModulesPaths,
10
10
  getRelativePath,
11
- getNonDDCallSiteFrames,
11
+ getCallSiteFramesForLocation,
12
12
  ddBasePath, // Exported only for test purposes
13
13
  }
14
14
 
15
15
  const EXCLUDED_PATHS = [
16
16
  path.join(path.sep, 'node_modules', 'dc-polyfill'),
17
17
  ]
18
- const EXCLUDED_PATH_PREFIXES = [
19
- 'node:diagnostics_channel',
20
- 'diagnostics_channel',
21
- 'node:child_process',
22
- 'child_process',
23
- 'node:async_hooks',
24
- 'async_hooks',
25
- 'node:internal/async_local_storage',
26
- ]
27
18
 
28
- function getNonDDCallSiteFrames (callSiteFrames, externallyExcludedPaths) {
19
+ /**
20
+ * Processes and filters call site frames to find the best location for a vulnerability.
21
+ * Returns client frames if available, otherwise falls back to all processed frames.
22
+ * Excludes dd-trace frames and all Node.js built-in/internal modules (node:*).
23
+ * @param {CallSiteFrame[]} callSiteFrames
24
+ * @param {string[]} externallyExcludedPaths
25
+ * @returns {CallSiteFrame[]} Client frames if available, otherwise all processed frames
26
+ *
27
+ * @typedef {object} CallSiteFrame
28
+ * @property {number} id
29
+ * @property {string} file - Original file path
30
+ * @property {number} line
31
+ * @property {number} column
32
+ * @property {string} function
33
+ * @property {string} class_name
34
+ * @property {boolean} isNative
35
+ * @property {string} [path] - Relative path, added during processing
36
+ * @property {boolean} [isInternal] - Whether the frame is internal, added during processing
37
+ */
38
+ function getCallSiteFramesForLocation (callSiteFrames, externallyExcludedPaths) {
29
39
  if (!callSiteFrames) {
30
40
  return []
31
41
  }
32
42
 
33
- const result = []
43
+ const allFrames = []
44
+ const clientFrames = []
34
45
 
35
46
  for (const callsite of callSiteFrames) {
36
47
  let filepath = callsite.file?.startsWith('file://') ? fileURLToPath(callsite.file) : callsite.file
@@ -47,18 +58,22 @@ function getNonDDCallSiteFrames (callSiteFrames, externallyExcludedPaths) {
47
58
  callsite.column = column
48
59
  }
49
60
 
50
- if (
51
- !isExcluded(callsite, externallyExcludedPaths) &&
52
- (!filepath.includes(pathLine.ddBasePath) || globalThis.__DD_ESBUILD_IAST_WITH_NO_SM)
53
- ) {
61
+ if (filepath) {
54
62
  callsite.path = getRelativePath(filepath)
55
63
  callsite.isInternal = !path.isAbsolute(filepath)
56
64
 
57
- result.push(callsite)
65
+ allFrames.push(callsite)
66
+
67
+ if (
68
+ !isExcluded(callsite, externallyExcludedPaths) &&
69
+ (!filepath.includes(pathLine.ddBasePath) || globalThis.__DD_ESBUILD_IAST_WITH_NO_SM)
70
+ ) {
71
+ clientFrames.push(callsite)
72
+ }
58
73
  }
59
74
  }
60
75
 
61
- return result
76
+ return clientFrames.length > 0 ? clientFrames : allFrames
62
77
  }
63
78
 
64
79
  function getRelativePath (filepath) {
@@ -67,10 +82,12 @@ function getRelativePath (filepath) {
67
82
 
68
83
  function isExcluded (callsite, externallyExcludedPaths) {
69
84
  if (callsite.isNative) return true
85
+
70
86
  const filename = globalThis.__DD_ESBUILD_IAST_WITH_SM ? callsite.path : callsite.file
71
- if (!filename) {
87
+ if (!filename || filename.startsWith('node:')) {
72
88
  return true
73
89
  }
90
+
74
91
  let excludedPaths = EXCLUDED_PATHS
75
92
  if (externallyExcludedPaths) {
76
93
  excludedPaths = [...excludedPaths, ...externallyExcludedPaths]
@@ -82,12 +99,6 @@ function isExcluded (callsite, externallyExcludedPaths) {
82
99
  }
83
100
  }
84
101
 
85
- for (const EXCLUDED_PATH_PREFIX of EXCLUDED_PATH_PREFIXES) {
86
- if (filename.indexOf(EXCLUDED_PATH_PREFIX) === 0) {
87
- return true
88
- }
89
- }
90
-
91
102
  return false
92
103
  }
93
104
 
@@ -10,7 +10,7 @@ module.exports = function extractSensitiveRanges (evidence) {
10
10
  pattern.lastIndex = 0
11
11
 
12
12
  const regexResult = pattern.exec(evidence.value)
13
- if (regexResult && regexResult.length > 1) {
13
+ if (regexResult?.length > 1) {
14
14
  const start = regexResult.index + (regexResult[0].length - regexResult[1].length)
15
15
  const end = start + regexResult[1].length
16
16
  return [{ start, end }]
@@ -3,6 +3,7 @@
3
3
 
4
4
  const log = require('../../../../log')
5
5
  const vulnerabilities = require('../../vulnerabilities')
6
+ const defaults = require('../../../../config/defaults')
6
7
 
7
8
  const { contains, intersects, remove } = require('./range-utils')
8
9
 
@@ -14,14 +15,12 @@ const sqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyz
14
15
  const taintedRangeBasedSensitiveAnalyzer = require('./sensitive-analyzers/tainted-range-based-sensitive-analyzer')
15
16
  const urlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
16
17
 
17
- const { DEFAULT_IAST_REDACTION_NAME_PATTERN, DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./sensitive-regex')
18
-
19
18
  const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
20
19
 
21
20
  class SensitiveHandler {
22
21
  constructor () {
23
- this._namePattern = new RegExp(DEFAULT_IAST_REDACTION_NAME_PATTERN, 'gmi')
24
- this._valuePattern = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
22
+ this._namePattern = new RegExp(/** @type {string} */ (defaults['iast.redactionNamePattern']), 'gmi')
23
+ this._valuePattern = new RegExp(/** @type {string} */ (defaults['iast.redactionValuePattern']), 'gmi')
25
24
 
26
25
  this._sensitiveAnalyzers = new Map()
27
26
  this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, taintedRangeBasedSensitiveAnalyzer)