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.
- 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-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-mocha/src/index.js +3 -1
- 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 +4 -0
- 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/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', ({
|
|
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
|
|
@@ -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
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
function canAddVulnerability (vulnerability) {
|
|
25
|
+
const hasRequiredFields = vulnerability?.evidence && vulnerability?.type && vulnerability?.location
|
|
26
|
+
if (!hasRequiredFields) return false
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
const isDuplicated = deduplicationEnabled && isDuplicatedVulnerability(vulnerability)
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
return !isDuplicated
|
|
31
|
+
}
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
type: 'vulnerability'
|
|
30
|
-
})
|
|
33
|
+
function addVulnerability (iastContext, vulnerability, callSiteFrames) {
|
|
34
|
+
if (!canAddVulnerability(vulnerability)) return
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
VULNERABILITY_HASHES.set(`${vulnerability.type}${vulnerability.hash}`, true)
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
[IAST_ENABLED_TAG_KEY]: 1
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
+
let span = iastContext?.rootSpan
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
if (!span && tracer) {
|
|
41
|
+
span = tracer.startSpan('vulnerability', {
|
|
42
|
+
type: 'vulnerability'
|
|
43
|
+
})
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
standalone.sample(span)
|
|
45
|
+
vulnerability.location.spanId = span.context().toSpanId()
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
config.appsec.stackTrace.maxStackTraces
|
|
44
|
+
frames
|
|
40
45
|
)
|
|
41
46
|
}
|
|
42
47
|
|