dd-trace 5.32.0 → 5.33.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 (38) hide show
  1. package/README.md +12 -11
  2. package/index.d.ts +11 -1
  3. package/package.json +2 -2
  4. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +17 -9
  6. package/packages/datadog-instrumentations/src/jest.js +36 -21
  7. package/packages/datadog-instrumentations/src/mocha/main.js +9 -4
  8. package/packages/datadog-instrumentations/src/mocha/utils.js +4 -2
  9. package/packages/datadog-instrumentations/src/mocha/worker.js +4 -2
  10. package/packages/datadog-instrumentations/src/playwright.js +8 -3
  11. package/packages/datadog-instrumentations/src/vitest.js +35 -11
  12. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +16 -0
  13. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +63 -0
  14. package/packages/datadog-plugin-aws-sdk/src/services/{bedrockruntime.js → bedrockruntime/utils.js} +67 -75
  15. package/packages/datadog-plugin-cucumber/src/index.js +3 -1
  16. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -8
  17. package/packages/datadog-plugin-cypress/src/support.js +6 -2
  18. package/packages/datadog-plugin-fetch/src/index.js +3 -3
  19. package/packages/datadog-plugin-http/src/client.js +5 -33
  20. package/packages/datadog-plugin-jest/src/index.js +4 -1
  21. package/packages/datadog-plugin-mocha/src/index.js +3 -1
  22. package/packages/datadog-plugin-playwright/src/index.js +3 -1
  23. package/packages/datadog-plugin-vitest/src/index.js +16 -4
  24. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +3 -3
  25. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +41 -24
  26. package/packages/dd-trace/src/appsec/iast/iast-context.js +12 -0
  27. package/packages/dd-trace/src/appsec/iast/path-line.js +19 -23
  28. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -0
  29. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +75 -24
  30. package/packages/dd-trace/src/appsec/rasp/utils.js +10 -5
  31. package/packages/dd-trace/src/appsec/stack_trace.js +38 -28
  32. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -4
  33. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -3
  34. package/packages/dd-trace/src/config.js +4 -0
  35. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +59 -0
  36. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -0
  37. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +0 -2
  38. package/packages/dd-trace/src/plugins/util/test.js +2 -0
@@ -59,6 +59,11 @@ class HttpClientPlugin extends ClientPlugin {
59
59
  }
60
60
 
61
61
  if (this.shouldInjectTraceHeaders(options, uri)) {
62
+ // Clone the headers object in case an upstream lib has a reference to the original headers
63
+ // Implemented due to aws-sdk issue where request signing is broken if we mutate the headers
64
+ // Explained further in:
65
+ // https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1609#issuecomment-1826167348
66
+ options.headers = Object.assign({}, options.headers)
62
67
  this.tracer.inject(span, HTTP_HEADERS, options.headers)
63
68
  }
64
69
 
@@ -72,10 +77,6 @@ class HttpClientPlugin extends ClientPlugin {
72
77
  }
73
78
 
74
79
  shouldInjectTraceHeaders (options, uri) {
75
- if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
76
- return false
77
- }
78
-
79
80
  if (!this.config.propagationFilter(uri)) {
80
81
  return false
81
82
  }
@@ -212,31 +213,6 @@ function getHooks (config) {
212
213
  return { request }
213
214
  }
214
215
 
215
- function hasAmazonSignature (options) {
216
- if (!options) {
217
- return false
218
- }
219
-
220
- if (options.headers) {
221
- const headers = Object.keys(options.headers)
222
- .reduce((prev, next) => Object.assign(prev, {
223
- [next.toLowerCase()]: options.headers[next]
224
- }), {})
225
-
226
- if (headers['x-amz-signature']) {
227
- return true
228
- }
229
-
230
- if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
231
- return true
232
- }
233
- }
234
-
235
- const search = options.search || options.path
236
-
237
- return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1
238
- }
239
-
240
216
  function extractSessionDetails (options) {
241
217
  if (typeof options === 'string') {
242
218
  return new URL(options).host
@@ -248,8 +224,4 @@ function extractSessionDetails (options) {
248
224
  return { host, port }
249
225
  }
250
226
 
251
- function startsWith (searchString) {
252
- return value => String(value).startsWith(searchString)
253
- }
254
-
255
227
  module.exports = HttpClientPlugin
@@ -23,7 +23,8 @@ const {
23
23
  JEST_DISPLAY_NAME,
24
24
  TEST_IS_RUM_ACTIVE,
25
25
  TEST_BROWSER_DRIVER,
26
- getFormattedError
26
+ getFormattedError,
27
+ TEST_RETRY_REASON
27
28
  } = require('../../dd-trace/src/plugins/util/test')
28
29
  const { COMPONENT } = require('../../dd-trace/src/constants')
29
30
  const id = require('../../dd-trace/src/id')
@@ -167,6 +168,7 @@ class JestPlugin extends CiPlugin {
167
168
  config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
168
169
  config._ddFlakyTestRetriesCount = this.libraryConfig?.flakyTestRetriesCount
169
170
  config._ddIsDiEnabled = this.libraryConfig?.isDiEnabled ?? false
171
+ config._ddIsKnownTestsEnabled = this.libraryConfig?.isKnownTestsEnabled ?? false
170
172
  })
171
173
  })
172
174
 
@@ -410,6 +412,7 @@ class JestPlugin extends CiPlugin {
410
412
  extraTags[TEST_IS_NEW] = 'true'
411
413
  if (isEfdRetry) {
412
414
  extraTags[TEST_IS_RETRY] = 'true'
415
+ extraTags[TEST_RETRY_REASON] = 'efd'
413
416
  }
414
417
  }
415
418
 
@@ -30,7 +30,8 @@ const {
30
30
  TEST_SUITE,
31
31
  MOCHA_IS_PARALLEL,
32
32
  TEST_IS_RUM_ACTIVE,
33
- TEST_BROWSER_DRIVER
33
+ TEST_BROWSER_DRIVER,
34
+ TEST_RETRY_REASON
34
35
  } = require('../../dd-trace/src/plugins/util/test')
35
36
  const { COMPONENT } = require('../../dd-trace/src/constants')
36
37
  const {
@@ -421,6 +422,7 @@ class MochaPlugin extends CiPlugin {
421
422
  extraTags[TEST_IS_NEW] = 'true'
422
423
  if (isEfdRetry) {
423
424
  extraTags[TEST_IS_RETRY] = 'true'
425
+ extraTags[TEST_RETRY_REASON] = 'efd'
424
426
  }
425
427
  }
426
428
 
@@ -15,7 +15,8 @@ const {
15
15
  TEST_IS_NEW,
16
16
  TEST_IS_RETRY,
17
17
  TEST_EARLY_FLAKE_ENABLED,
18
- TELEMETRY_TEST_SESSION
18
+ TELEMETRY_TEST_SESSION,
19
+ TEST_RETRY_REASON
19
20
  } = require('../../dd-trace/src/plugins/util/test')
20
21
  const { RESOURCE_NAME } = require('../../../ext/tags')
21
22
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -144,6 +145,7 @@ class PlaywrightPlugin extends CiPlugin {
144
145
  span.setTag(TEST_IS_NEW, 'true')
145
146
  if (isEfdRetry) {
146
147
  span.setTag(TEST_IS_RETRY, 'true')
148
+ span.setTag(TEST_RETRY_REASON, 'efd')
147
149
  }
148
150
  }
149
151
  if (isRetry) {
@@ -17,7 +17,8 @@ const {
17
17
  TEST_SOURCE_START,
18
18
  TEST_IS_NEW,
19
19
  TEST_EARLY_FLAKE_ENABLED,
20
- TEST_EARLY_FLAKE_ABORT_REASON
20
+ TEST_EARLY_FLAKE_ABORT_REASON,
21
+ TEST_RETRY_REASON
21
22
  } = require('../../dd-trace/src/plugins/util/test')
22
23
  const { COMPONENT } = require('../../dd-trace/src/constants')
23
24
  const {
@@ -60,7 +61,14 @@ class VitestPlugin extends CiPlugin {
60
61
  onDone(isFaulty)
61
62
  })
62
63
 
63
- this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath, isRetry, isNew, mightHitProbe }) => {
64
+ this.addSub('ci:vitest:test:start', ({
65
+ testName,
66
+ testSuiteAbsolutePath,
67
+ isRetry,
68
+ isNew,
69
+ mightHitProbe,
70
+ isRetryReasonEfd
71
+ }) => {
64
72
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
65
73
  const store = storage.getStore()
66
74
 
@@ -73,6 +81,9 @@ class VitestPlugin extends CiPlugin {
73
81
  if (isNew) {
74
82
  extraTags[TEST_IS_NEW] = 'true'
75
83
  }
84
+ if (isRetryReasonEfd) {
85
+ extraTags[TEST_RETRY_REASON] = 'efd'
86
+ }
76
87
 
77
88
  const span = this.startTestSpan(
78
89
  testName,
@@ -147,7 +158,7 @@ class VitestPlugin extends CiPlugin {
147
158
  }
148
159
  })
149
160
 
150
- this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath }) => {
161
+ this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath, isNew }) => {
151
162
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
152
163
  const testSpan = this.startTestSpan(
153
164
  testName,
@@ -156,7 +167,8 @@ class VitestPlugin extends CiPlugin {
156
167
  {
157
168
  [TEST_SOURCE_FILE]: testSuite,
158
169
  [TEST_SOURCE_START]: 1, // we can't get the proper start line in vitest
159
- [TEST_STATUS]: 'skip'
170
+ [TEST_STATUS]: 'skip',
171
+ ...(isNew ? { [TEST_IS_NEW]: 'true' } : {})
160
172
  }
161
173
  )
162
174
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
@@ -54,15 +54,15 @@ class CookieAnalyzer extends Analyzer {
54
54
  return super._checkOCE(context, value)
55
55
  }
56
56
 
57
- _getLocation (value) {
57
+ _getLocation (value, callSiteFrames) {
58
58
  if (!value) {
59
- return super._getLocation()
59
+ return super._getLocation(value, callSiteFrames)
60
60
  }
61
61
 
62
62
  if (value.location) {
63
63
  return value.location
64
64
  }
65
- const location = super._getLocation(value)
65
+ const location = super._getLocation(value, callSiteFrames)
66
66
  value.location = location
67
67
  return location
68
68
  }
@@ -1,12 +1,15 @@
1
1
  'use strict'
2
2
 
3
3
  const { storage } = require('../../../../../datadog-core')
4
- const { getFirstNonDDPathAndLine } = require('../path-line')
5
- const { addVulnerability } = require('../vulnerability-reporter')
6
- const { getIastContext } = require('../iast-context')
4
+ const { getNonDDCallSiteFrames } = require('../path-line')
5
+ const { getIastContext, getIastStackTraceId } = require('../iast-context')
7
6
  const overheadController = require('../overhead-controller')
8
7
  const { SinkIastPlugin } = require('../iast-plugin')
9
- const { getOriginalPathAndLineFromSourceMap } = require('../taint-tracking/rewriter')
8
+ const {
9
+ addVulnerability,
10
+ getVulnerabilityCallSiteFrames,
11
+ replaceCallSiteFromSourceMap
12
+ } = require('../vulnerability-reporter')
10
13
 
11
14
  class Analyzer extends SinkIastPlugin {
12
15
  constructor (type) {
@@ -28,12 +31,24 @@ class Analyzer extends SinkIastPlugin {
28
31
  }
29
32
 
30
33
  _reportEvidence (value, context, evidence) {
31
- const location = this._getLocation(value)
34
+ const callSiteFrames = getVulnerabilityCallSiteFrames()
35
+ const nonDDCallSiteFrames = getNonDDCallSiteFrames(callSiteFrames, this._getExcludedPaths())
36
+
37
+ const location = this._getLocation(value, nonDDCallSiteFrames)
38
+
32
39
  if (!this._isExcluded(location)) {
33
- const locationSourceMap = this._replaceLocationFromSourceMap(location)
40
+ const originalLocation = this._getOriginalLocation(location)
34
41
  const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
35
- const vulnerability = this._createVulnerability(this._type, evidence, spanId, locationSourceMap)
36
- addVulnerability(context, vulnerability)
42
+ const stackId = getIastStackTraceId(context)
43
+ const vulnerability = this._createVulnerability(
44
+ this._type,
45
+ evidence,
46
+ spanId,
47
+ originalLocation,
48
+ stackId
49
+ )
50
+
51
+ addVulnerability(context, vulnerability, nonDDCallSiteFrames)
37
52
  }
38
53
  }
39
54
 
@@ -49,24 +64,25 @@ class Analyzer extends SinkIastPlugin {
49
64
  return { value }
50
65
  }
51
66
 
52
- _getLocation () {
53
- return getFirstNonDDPathAndLine(this._getExcludedPaths())
67
+ _getLocation (value, callSiteFrames) {
68
+ return callSiteFrames[0]
54
69
  }
55
70
 
56
- _replaceLocationFromSourceMap (location) {
57
- if (location) {
58
- const { path, line, column } = getOriginalPathAndLineFromSourceMap(location)
59
- if (path) {
60
- location.path = path
61
- }
62
- if (line) {
63
- location.line = line
64
- }
65
- if (column) {
66
- location.column = column
67
- }
71
+ _getOriginalLocation (location) {
72
+ const locationFromSourceMap = replaceCallSiteFromSourceMap(location)
73
+ const originalLocation = {}
74
+
75
+ if (locationFromSourceMap?.path) {
76
+ originalLocation.path = locationFromSourceMap.path
77
+ }
78
+ if (locationFromSourceMap?.line) {
79
+ originalLocation.line = locationFromSourceMap.line
68
80
  }
69
- return location
81
+ if (locationFromSourceMap?.column) {
82
+ originalLocation.column = locationFromSourceMap.column
83
+ }
84
+
85
+ return originalLocation
70
86
  }
71
87
 
72
88
  _getExcludedPaths () {}
@@ -102,12 +118,13 @@ class Analyzer extends SinkIastPlugin {
102
118
  return overheadController.hasQuota(overheadController.OPERATIONS.REPORT_VULNERABILITY, context)
103
119
  }
104
120
 
105
- _createVulnerability (type, evidence, spanId, location) {
121
+ _createVulnerability (type, evidence, spanId, location, stackId) {
106
122
  if (type && evidence) {
107
123
  const _spanId = spanId || 0
108
124
  return {
109
125
  type,
110
126
  evidence,
127
+ stackId,
111
128
  location: {
112
129
  spanId: _spanId,
113
130
  ...location
@@ -9,6 +9,17 @@ function getIastContext (store, topContext) {
9
9
  return iastContext
10
10
  }
11
11
 
12
+ function getIastStackTraceId (iastContext) {
13
+ if (!iastContext) return 0
14
+
15
+ if (!iastContext.stackTraceId) {
16
+ iastContext.stackTraceId = 0
17
+ }
18
+
19
+ iastContext.stackTraceId += 1
20
+ return iastContext.stackTraceId
21
+ }
22
+
12
23
  /* TODO Fix storage problem when the close event is called without
13
24
  finish event to remove `topContext` references
14
25
  We have to save the context in two places, because
@@ -51,6 +62,7 @@ module.exports = {
51
62
  getIastContext,
52
63
  saveIastContext,
53
64
  cleanIastContext,
65
+ getIastStackTraceId,
54
66
  IAST_CONTEXT_KEY,
55
67
  IAST_TRANSACTION_ID
56
68
  }
@@ -3,12 +3,10 @@
3
3
  const path = require('path')
4
4
  const process = require('process')
5
5
  const { calculateDDBasePath } = require('../../util')
6
- const { getCallSiteList } = require('../stack_trace')
7
6
  const pathLine = {
8
- getFirstNonDDPathAndLine,
9
7
  getNodeModulesPaths,
10
8
  getRelativePath,
11
- getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
9
+ getNonDDCallSiteFrames,
12
10
  calculateDDBasePath, // Exported only for test purposes
13
11
  ddBasePath: calculateDDBasePath(__dirname) // Only for test purposes
14
12
  }
@@ -25,22 +23,24 @@ const EXCLUDED_PATH_PREFIXES = [
25
23
  'async_hooks'
26
24
  ]
27
25
 
28
- function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPaths) {
29
- if (callsites) {
30
- for (let i = 0; i < callsites.length; i++) {
31
- const callsite = callsites[i]
32
- const filepath = callsite.getFileName()
33
- if (!isExcluded(callsite, externallyExcludedPaths) && filepath.indexOf(pathLine.ddBasePath) === -1) {
34
- return {
35
- path: getRelativePath(filepath),
36
- line: callsite.getLineNumber(),
37
- column: callsite.getColumnNumber(),
38
- isInternal: !path.isAbsolute(filepath)
39
- }
40
- }
26
+ function getNonDDCallSiteFrames (callSiteFrames, externallyExcludedPaths) {
27
+ if (!callSiteFrames) {
28
+ return []
29
+ }
30
+
31
+ const result = []
32
+
33
+ for (const callsite of callSiteFrames) {
34
+ const filepath = callsite.file
35
+ if (!isExcluded(callsite, externallyExcludedPaths) && filepath.indexOf(pathLine.ddBasePath) === -1) {
36
+ callsite.path = getRelativePath(filepath)
37
+ callsite.isInternal = !path.isAbsolute(filepath)
38
+
39
+ result.push(callsite)
41
40
  }
42
41
  }
43
- return null
42
+
43
+ return result
44
44
  }
45
45
 
46
46
  function getRelativePath (filepath) {
@@ -48,8 +48,8 @@ function getRelativePath (filepath) {
48
48
  }
49
49
 
50
50
  function isExcluded (callsite, externallyExcludedPaths) {
51
- if (callsite.isNative()) return true
52
- const filename = callsite.getFileName()
51
+ if (callsite.isNative) return true
52
+ const filename = callsite.file
53
53
  if (!filename) {
54
54
  return true
55
55
  }
@@ -73,10 +73,6 @@ function isExcluded (callsite, externallyExcludedPaths) {
73
73
  return false
74
74
  }
75
75
 
76
- function getFirstNonDDPathAndLine (externallyExcludedPaths) {
77
- return getFirstNonDDPathAndLineFromCallsites(getCallSiteList(), externallyExcludedPaths)
78
- }
79
-
80
76
  function getNodeModulesPaths (...paths) {
81
77
  const nodeModulesPaths = []
82
78
 
@@ -84,6 +84,7 @@ class VulnerabilityFormatter {
84
84
  const formattedVulnerability = {
85
85
  type: vulnerability.type,
86
86
  hash: vulnerability.hash,
87
+ stackId: vulnerability.stackId,
87
88
  evidence: this.formatEvidence(vulnerability.type, vulnerability.evidence, sourcesIndexes, sources),
88
89
  location: {
89
90
  spanId: vulnerability.location.spanId
@@ -6,6 +6,8 @@ const { IAST_ENABLED_TAG_KEY, IAST_JSON_TAG_KEY } = require('./tags')
6
6
  const standalone = require('../standalone')
7
7
  const { SAMPLING_MECHANISM_APPSEC } = require('../../constants')
8
8
  const { keepTrace } = require('../../priority_sampler')
9
+ const { reportStackTrace, getCallsiteFrames, canReportStackTrace, STACK_TRACE_NAMESPACES } = require('../stack_trace')
10
+ const { getOriginalPathAndLineFromSourceMap } = require('./taint-tracking/rewriter')
9
11
 
10
12
  const VULNERABILITIES_KEY = 'vulnerabilities'
11
13
  const VULNERABILITY_HASHES_MAX_SIZE = 1000
@@ -15,39 +17,60 @@ const RESET_VULNERABILITY_CACHE_INTERVAL = 60 * 60 * 1000 // 1 hour
15
17
  let tracer
16
18
  let resetVulnerabilityCacheTimer
17
19
  let deduplicationEnabled = true
20
+ let stackTraceEnabled = true
21
+ let stackTraceMaxDepth
22
+ let maxStackTraces
18
23
 
19
- function addVulnerability (iastContext, vulnerability) {
20
- if (vulnerability?.evidence && vulnerability?.type && vulnerability?.location) {
21
- if (deduplicationEnabled && isDuplicatedVulnerability(vulnerability)) return
24
+ function canAddVulnerability (vulnerability) {
25
+ const hasRequiredFields = vulnerability?.evidence && vulnerability?.type && vulnerability?.location
26
+ if (!hasRequiredFields) return false
22
27
 
23
- VULNERABILITY_HASHES.set(`${vulnerability.type}${vulnerability.hash}`, true)
28
+ const isDuplicated = deduplicationEnabled && isDuplicatedVulnerability(vulnerability)
24
29
 
25
- let span = iastContext?.rootSpan
30
+ return !isDuplicated
31
+ }
26
32
 
27
- if (!span && tracer) {
28
- span = tracer.startSpan('vulnerability', {
29
- type: 'vulnerability'
30
- })
33
+ function addVulnerability (iastContext, vulnerability, callSiteFrames) {
34
+ if (!canAddVulnerability(vulnerability)) return
31
35
 
32
- vulnerability.location.spanId = span.context().toSpanId()
36
+ VULNERABILITY_HASHES.set(`${vulnerability.type}${vulnerability.hash}`, true)
33
37
 
34
- span.addTags({
35
- [IAST_ENABLED_TAG_KEY]: 1
36
- })
37
- }
38
+ let span = iastContext?.rootSpan
38
39
 
39
- if (!span) return
40
+ if (!span && tracer) {
41
+ span = tracer.startSpan('vulnerability', {
42
+ type: 'vulnerability'
43
+ })
40
44
 
41
- keepTrace(span, SAMPLING_MECHANISM_APPSEC)
42
- standalone.sample(span)
45
+ vulnerability.location.spanId = span.context().toSpanId()
43
46
 
44
- if (iastContext?.rootSpan) {
45
- iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
46
- iastContext[VULNERABILITIES_KEY].push(vulnerability)
47
- } else {
48
- sendVulnerabilities([vulnerability], span)
49
- span.finish()
50
- }
47
+ span.addTags({
48
+ [IAST_ENABLED_TAG_KEY]: 1
49
+ })
50
+ }
51
+
52
+ if (!span) return
53
+
54
+ keepTrace(span, SAMPLING_MECHANISM_APPSEC)
55
+ standalone.sample(span)
56
+
57
+ if (stackTraceEnabled && canReportStackTrace(span, maxStackTraces, STACK_TRACE_NAMESPACES.IAST)) {
58
+ const originalCallSiteList = callSiteFrames.map(callsite => replaceCallSiteFromSourceMap(callsite))
59
+
60
+ reportStackTrace(
61
+ span,
62
+ vulnerability.stackId,
63
+ originalCallSiteList,
64
+ STACK_TRACE_NAMESPACES.IAST
65
+ )
66
+ }
67
+
68
+ if (iastContext?.rootSpan) {
69
+ iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
70
+ iastContext[VULNERABILITIES_KEY].push(vulnerability)
71
+ } else {
72
+ sendVulnerabilities([vulnerability], span)
73
+ span.finish()
51
74
  }
52
75
  }
53
76
 
@@ -94,8 +117,34 @@ function isDuplicatedVulnerability (vulnerability) {
94
117
  return VULNERABILITY_HASHES.get(`${vulnerability.type}${vulnerability.hash}`)
95
118
  }
96
119
 
120
+ function getVulnerabilityCallSiteFrames () {
121
+ return getCallsiteFrames(stackTraceMaxDepth)
122
+ }
123
+
124
+ function replaceCallSiteFromSourceMap (callsite) {
125
+ if (callsite) {
126
+ const { path, line, column } = getOriginalPathAndLineFromSourceMap(callsite)
127
+ if (path) {
128
+ callsite.file = path
129
+ callsite.path = path
130
+ }
131
+ if (line) {
132
+ callsite.line = line
133
+ }
134
+ if (column) {
135
+ callsite.column = column
136
+ }
137
+ }
138
+
139
+ return callsite
140
+ }
141
+
97
142
  function start (config, _tracer) {
98
143
  deduplicationEnabled = config.iast.deduplicationEnabled
144
+ stackTraceEnabled = config.iast.stackTrace.enabled
145
+ stackTraceMaxDepth = config.appsec.stackTrace.maxDepth
146
+ maxStackTraces = config.appsec.stackTrace.maxStackTraces
147
+
99
148
  vulnerabilitiesFormatter.setRedactVulnerabilities(
100
149
  config.iast.redactionEnabled,
101
150
  config.iast.redactionNamePattern,
@@ -114,6 +163,8 @@ function stop () {
114
163
  module.exports = {
115
164
  addVulnerability,
116
165
  sendVulnerabilities,
166
+ getVulnerabilityCallSiteFrames,
167
+ replaceCallSiteFromSourceMap,
117
168
  clearCache,
118
169
  start,
119
170
  stop
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const web = require('../../plugins/util/web')
4
- const { reportStackTrace } = require('../stack_trace')
4
+ const { getCallsiteFrames, reportStackTrace, canReportStackTrace } = require('../stack_trace')
5
5
  const { getBlockingAction } = require('../blocking')
6
6
  const log = require('../../log')
7
7
 
@@ -30,13 +30,18 @@ class DatadogRaspAbortError extends Error {
30
30
 
31
31
  function handleResult (actions, req, res, abortController, config) {
32
32
  const generateStackTraceAction = actions?.generate_stack
33
- if (generateStackTraceAction && config.appsec.stackTrace.enabled) {
34
- const rootSpan = web.root(req)
33
+
34
+ const { enabled, maxDepth, maxStackTraces } = config.appsec.stackTrace
35
+
36
+ const rootSpan = web.root(req)
37
+
38
+ if (generateStackTraceAction && enabled && canReportStackTrace(rootSpan, maxStackTraces)) {
39
+ const frames = getCallsiteFrames(maxDepth)
40
+
35
41
  reportStackTrace(
36
42
  rootSpan,
37
43
  generateStackTraceAction.stack_id,
38
- config.appsec.stackTrace.maxDepth,
39
- config.appsec.stackTrace.maxStackTraces
44
+ frames
40
45
  )
41
46
  }
42
47