dd-trace 5.32.0 → 5.33.1

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 (43) 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-amqplib/src/producer.js +9 -1
  13. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +16 -0
  14. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +63 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/{bedrockruntime.js → bedrockruntime/utils.js} +67 -75
  16. package/packages/datadog-plugin-cucumber/src/index.js +3 -1
  17. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -8
  18. package/packages/datadog-plugin-cypress/src/support.js +6 -2
  19. package/packages/datadog-plugin-fetch/src/index.js +3 -3
  20. package/packages/datadog-plugin-http/src/client.js +5 -33
  21. package/packages/datadog-plugin-jest/src/index.js +4 -1
  22. package/packages/datadog-plugin-langchain/src/handlers/default.js +6 -30
  23. package/packages/datadog-plugin-langchain/src/tracing.js +5 -6
  24. package/packages/datadog-plugin-mocha/src/index.js +3 -1
  25. package/packages/datadog-plugin-openai/src/tracing.js +14 -33
  26. package/packages/datadog-plugin-playwright/src/index.js +3 -1
  27. package/packages/datadog-plugin-vitest/src/index.js +16 -4
  28. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +3 -3
  29. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +41 -24
  30. package/packages/dd-trace/src/appsec/iast/iast-context.js +12 -0
  31. package/packages/dd-trace/src/appsec/iast/path-line.js +19 -23
  32. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -0
  33. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +75 -24
  34. package/packages/dd-trace/src/appsec/rasp/utils.js +10 -5
  35. package/packages/dd-trace/src/appsec/stack_trace.js +38 -28
  36. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -4
  37. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -3
  38. package/packages/dd-trace/src/config.js +6 -2
  39. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +59 -0
  40. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -0
  41. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +0 -2
  42. package/packages/dd-trace/src/plugins/util/llm.js +35 -0
  43. 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
 
@@ -1,16 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const Sampler = require('../../../dd-trace/src/sampler')
3
+ const makeUtilities = require('../../../dd-trace/src/plugins/util/llm')
4
4
 
5
- const RE_NEWLINE = /\n/g
6
- const RE_TAB = /\t/g
7
-
8
- // TODO: should probably refactor the OpenAI integration to use a shared LLMTracingPlugin base class
9
- // This logic isn't particular to LangChain
10
5
  class LangChainHandler {
11
- constructor (config) {
12
- this.config = config
13
- this.sampler = new Sampler(config.spanPromptCompletionSampleRate)
6
+ constructor (tracerConfig) {
7
+ const utilities = makeUtilities('langchain', tracerConfig)
8
+
9
+ this.normalize = utilities.normalize
10
+ this.isPromptCompletionSampled = utilities.isPromptCompletionSampled
14
11
  }
15
12
 
16
13
  // no-op for default handler
@@ -27,27 +24,6 @@ class LangChainHandler {
27
24
 
28
25
  // no-op for default handler
29
26
  extractModel (instance) {}
30
-
31
- normalize (text) {
32
- if (!text) return
33
- if (typeof text !== 'string' || !text || (typeof text === 'string' && text.length === 0)) return
34
-
35
- const max = this.config.spanCharLimit
36
-
37
- text = text
38
- .replace(RE_NEWLINE, '\\n')
39
- .replace(RE_TAB, '\\t')
40
-
41
- if (text.length > max) {
42
- return text.substring(0, max) + '...'
43
- }
44
-
45
- return text
46
- }
47
-
48
- isPromptCompletionSampled () {
49
- return this.sampler.isSampled()
50
- }
51
27
  }
52
28
 
53
29
  module.exports = LangChainHandler
@@ -26,13 +26,12 @@ class LangChainTracingPlugin extends TracingPlugin {
26
26
  constructor () {
27
27
  super(...arguments)
28
28
 
29
- const langchainConfig = this._tracerConfig.langchain || {}
30
29
  this.handlers = {
31
- chain: new LangChainChainHandler(langchainConfig),
32
- chat_model: new LangChainChatModelHandler(langchainConfig),
33
- llm: new LangChainLLMHandler(langchainConfig),
34
- embedding: new LangChainEmbeddingHandler(langchainConfig),
35
- default: new LangChainHandler(langchainConfig)
30
+ chain: new LangChainChainHandler(this._tracerConfig),
31
+ chat_model: new LangChainChatModelHandler(this._tracerConfig),
32
+ llm: new LangChainLLMHandler(this._tracerConfig),
33
+ embedding: new LangChainEmbeddingHandler(this._tracerConfig),
34
+ default: new LangChainHandler(this._tracerConfig)
36
35
  }
37
36
  }
38
37
 
@@ -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
 
@@ -9,12 +9,9 @@ const Sampler = require('../../dd-trace/src/sampler')
9
9
  const { MEASURED } = require('../../../ext/tags')
10
10
  const { estimateTokens } = require('./token-estimator')
11
11
 
12
- // String#replaceAll unavailable on Node.js@v14 (dd-trace@<=v3)
13
- const RE_NEWLINE = /\n/g
14
- const RE_TAB = /\t/g
12
+ const makeUtilities = require('../../dd-trace/src/plugins/util/llm')
15
13
 
16
- // TODO: In the future we should refactor config.js to make it requirable
17
- let MAX_TEXT_LEN = 128
14
+ let normalize
18
15
 
19
16
  function safeRequire (path) {
20
17
  try {
@@ -44,9 +41,11 @@ class OpenAiTracingPlugin extends TracingPlugin {
44
41
 
45
42
  this.sampler = new Sampler(0.1) // default 10% log sampling
46
43
 
47
- // hoist the max length env var to avoid making all of these functions a class method
44
+ // hoist the normalize function to avoid making all of these functions a class method
48
45
  if (this._tracerConfig) {
49
- MAX_TEXT_LEN = this._tracerConfig.openaiSpanCharLimit
46
+ const utilities = makeUtilities('openai', this._tracerConfig)
47
+
48
+ normalize = utilities.normalize
50
49
  }
51
50
  }
52
51
 
@@ -116,7 +115,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
116
115
  // createEdit, createEmbedding, createModeration
117
116
  if (payload.input) {
118
117
  const normalized = normalizeStringOrTokenArray(payload.input, false)
119
- tags['openai.request.input'] = truncateText(normalized)
118
+ tags['openai.request.input'] = normalize(normalized)
120
119
  openaiStore.input = normalized
121
120
  }
122
121
 
@@ -594,7 +593,7 @@ function commonImageResponseExtraction (tags, body) {
594
593
  for (let i = 0; i < body.data.length; i++) {
595
594
  const image = body.data[i]
596
595
  // exactly one of these two options is provided
597
- tags[`openai.response.images.${i}.url`] = truncateText(image.url)
596
+ tags[`openai.response.images.${i}.url`] = normalize(image.url)
598
597
  tags[`openai.response.images.${i}.b64_json`] = image.b64_json && 'returned'
599
598
  }
600
599
  }
@@ -731,14 +730,14 @@ function commonCreateResponseExtraction (tags, body, openaiStore, methodName) {
731
730
 
732
731
  tags[`openai.response.choices.${choiceIdx}.finish_reason`] = choice.finish_reason
733
732
  tags[`openai.response.choices.${choiceIdx}.logprobs`] = specifiesLogProb ? 'returned' : undefined
734
- tags[`openai.response.choices.${choiceIdx}.text`] = truncateText(choice.text)
733
+ tags[`openai.response.choices.${choiceIdx}.text`] = normalize(choice.text)
735
734
 
736
735
  // createChatCompletion only
737
736
  const message = choice.message || choice.delta // delta for streamed responses
738
737
  if (message) {
739
738
  tags[`openai.response.choices.${choiceIdx}.message.role`] = message.role
740
- tags[`openai.response.choices.${choiceIdx}.message.content`] = truncateText(message.content)
741
- tags[`openai.response.choices.${choiceIdx}.message.name`] = truncateText(message.name)
739
+ tags[`openai.response.choices.${choiceIdx}.message.content`] = normalize(message.content)
740
+ tags[`openai.response.choices.${choiceIdx}.message.name`] = normalize(message.name)
742
741
  if (message.tool_calls) {
743
742
  const toolCalls = message.tool_calls
744
743
  for (let toolIdx = 0; toolIdx < toolCalls.length; toolIdx++) {
@@ -795,24 +794,6 @@ function truncateApiKey (apiKey) {
795
794
  return apiKey && `sk-...${apiKey.substr(apiKey.length - 4)}`
796
795
  }
797
796
 
798
- /**
799
- * for cleaning up prompt and response
800
- */
801
- function truncateText (text) {
802
- if (!text) return
803
- if (typeof text !== 'string' || !text || (typeof text === 'string' && text.length === 0)) return
804
-
805
- text = text
806
- .replace(RE_NEWLINE, '\\n')
807
- .replace(RE_TAB, '\\t')
808
-
809
- if (text.length > MAX_TEXT_LEN) {
810
- return text.substring(0, MAX_TEXT_LEN) + '...'
811
- }
812
-
813
- return text
814
- }
815
-
816
797
  function tagChatCompletionRequestContent (contents, messageIdx, tags) {
817
798
  if (typeof contents === 'string') {
818
799
  tags[`openai.request.messages.${messageIdx}.content`] = contents
@@ -824,10 +805,10 @@ function tagChatCompletionRequestContent (contents, messageIdx, tags) {
824
805
  const type = content.type
825
806
  tags[`openai.request.messages.${messageIdx}.content.${contentIdx}.type`] = content.type
826
807
  if (type === 'text') {
827
- tags[`openai.request.messages.${messageIdx}.content.${contentIdx}.text`] = truncateText(content.text)
808
+ tags[`openai.request.messages.${messageIdx}.content.${contentIdx}.text`] = normalize(content.text)
828
809
  } else if (type === 'image_url') {
829
810
  tags[`openai.request.messages.${messageIdx}.content.${contentIdx}.image_url.url`] =
830
- truncateText(content.image_url.url)
811
+ normalize(content.image_url.url)
831
812
  }
832
813
  // unsupported type otherwise, won't be tagged
833
814
  }
@@ -1004,7 +985,7 @@ function normalizeStringOrTokenArray (input, truncate) {
1004
985
  const normalized = Array.isArray(input)
1005
986
  ? `[${input.join(', ')}]` // "[1, 2, 999]"
1006
987
  : input // "foo"
1007
- return truncate ? truncateText(normalized) : normalized
988
+ return truncate ? normalize(normalized) : normalized
1008
989
  }
1009
990
 
1010
991
  function defensiveArrayLength (maybeArray) {
@@ -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