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.
- package/README.md +12 -11
- package/index.d.ts +11 -1
- package/package.json +2 -2
- package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
- package/packages/datadog-instrumentations/src/cucumber.js +17 -9
- package/packages/datadog-instrumentations/src/jest.js +36 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +9 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +4 -2
- package/packages/datadog-instrumentations/src/mocha/worker.js +4 -2
- package/packages/datadog-instrumentations/src/playwright.js +8 -3
- package/packages/datadog-instrumentations/src/vitest.js +35 -11
- package/packages/datadog-plugin-amqplib/src/producer.js +9 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +16 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +63 -0
- package/packages/datadog-plugin-aws-sdk/src/services/{bedrockruntime.js → bedrockruntime/utils.js} +67 -75
- package/packages/datadog-plugin-cucumber/src/index.js +3 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -8
- package/packages/datadog-plugin-cypress/src/support.js +6 -2
- package/packages/datadog-plugin-fetch/src/index.js +3 -3
- package/packages/datadog-plugin-http/src/client.js +5 -33
- package/packages/datadog-plugin-jest/src/index.js +4 -1
- package/packages/datadog-plugin-langchain/src/handlers/default.js +6 -30
- package/packages/datadog-plugin-langchain/src/tracing.js +5 -6
- package/packages/datadog-plugin-mocha/src/index.js +3 -1
- package/packages/datadog-plugin-openai/src/tracing.js +14 -33
- package/packages/datadog-plugin-playwright/src/index.js +3 -1
- package/packages/datadog-plugin-vitest/src/index.js +16 -4
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +41 -24
- package/packages/dd-trace/src/appsec/iast/iast-context.js +12 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +19 -23
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +75 -24
- package/packages/dd-trace/src/appsec/rasp/utils.js +10 -5
- package/packages/dd-trace/src/appsec/stack_trace.js +38 -28
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -4
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -3
- package/packages/dd-trace/src/config.js +6 -2
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +59 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -0
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +0 -2
- package/packages/dd-trace/src/plugins/util/llm.js +35 -0
- 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
|
|
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 (
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
32
|
-
chat_model: new LangChainChatModelHandler(
|
|
33
|
-
llm: new LangChainLLMHandler(
|
|
34
|
-
embedding: new LangChainEmbeddingHandler(
|
|
35
|
-
default: new LangChainHandler(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
44
|
+
// hoist the normalize function to avoid making all of these functions a class method
|
|
48
45
|
if (this._tracerConfig) {
|
|
49
|
-
|
|
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'] =
|
|
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`] =
|
|
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`] =
|
|
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`] =
|
|
741
|
-
tags[`openai.response.choices.${choiceIdx}.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`] =
|
|
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
|
-
|
|
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 ?
|
|
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', ({
|
|
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 {
|
|
5
|
-
const {
|
|
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 {
|
|
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
|
|
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
|
|
40
|
+
const originalLocation = this._getOriginalLocation(location)
|
|
34
41
|
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
35
|
-
const
|
|
36
|
-
|
|
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
|
|
67
|
+
_getLocation (value, callSiteFrames) {
|
|
68
|
+
return callSiteFrames[0]
|
|
54
69
|
}
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
52
|
-
const filename = callsite.
|
|
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
|