dd-trace 3.37.0 → 3.38.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/LICENSE-3rdparty.csv +1 -0
- package/package.json +4 -3
- package/packages/datadog-instrumentations/src/body-parser.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +24 -4
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
- package/packages/datadog-instrumentations/src/express.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/jest.js +20 -11
- package/packages/datadog-instrumentations/src/knex.js +62 -1
- package/packages/datadog-instrumentations/src/mocha.js +19 -4
- package/packages/datadog-instrumentations/src/mongodb.js +63 -0
- package/packages/datadog-instrumentations/src/mongoose.js +140 -1
- package/packages/datadog-instrumentations/src/next.js +40 -0
- package/packages/datadog-instrumentations/src/playwright.js +11 -2
- package/packages/datadog-plugin-cucumber/src/index.js +17 -5
- package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
- package/packages/datadog-plugin-jest/src/index.js +19 -4
- package/packages/datadog-plugin-jest/src/util.js +45 -2
- package/packages/datadog-plugin-memcached/src/index.js +10 -5
- package/packages/datadog-plugin-mocha/src/index.js +19 -6
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +166 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +13 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +31 -13
- package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +14 -1
- package/packages/dd-trace/src/config.js +8 -0
- package/packages/dd-trace/src/format.js +3 -0
- package/packages/dd-trace/src/plugin_manager.js +3 -1
- package/packages/dd-trace/src/plugins/util/ci.js +17 -0
- package/packages/dd-trace/src/plugins/util/git.js +26 -4
- package/packages/dd-trace/src/plugins/util/test.js +16 -1
- package/packages/dd-trace/src/profiling/config.js +36 -5
- package/packages/dd-trace/src/profiling/profilers/wall.js +7 -1
- package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
- package/packages/dd-trace/src/telemetry/metrics.js +0 -5
|
@@ -181,6 +181,15 @@ function dispatcherHook (dispatcherExport) {
|
|
|
181
181
|
return dispatcherExport
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
function getTestByTestId (dispatcher, testId) {
|
|
185
|
+
if (dispatcher._testById) {
|
|
186
|
+
return dispatcher._testById.get(testId)?.test
|
|
187
|
+
}
|
|
188
|
+
if (dispatcher._allTests) {
|
|
189
|
+
return dispatcher._allTests.find(({ id }) => id === testId)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
184
193
|
function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
185
194
|
shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', runWrapper)
|
|
186
195
|
shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
|
|
@@ -188,11 +197,11 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
188
197
|
const worker = createWorker.apply(this, arguments)
|
|
189
198
|
|
|
190
199
|
worker.on('testBegin', ({ testId }) => {
|
|
191
|
-
const
|
|
200
|
+
const test = getTestByTestId(dispatcher, testId)
|
|
192
201
|
testBeginHandler(test)
|
|
193
202
|
})
|
|
194
203
|
worker.on('testEnd', ({ testId, status, errors }) => {
|
|
195
|
-
const
|
|
204
|
+
const test = getTestByTestId(dispatcher, testId)
|
|
196
205
|
|
|
197
206
|
testEndHandler(test, STATUS_TO_TEST_STATUS[status], errors && errors[0])
|
|
198
207
|
})
|
|
@@ -10,7 +10,9 @@ const {
|
|
|
10
10
|
finishAllTraceSpans,
|
|
11
11
|
getTestSuitePath,
|
|
12
12
|
getTestSuiteCommonTags,
|
|
13
|
-
addIntelligentTestRunnerSpanTags
|
|
13
|
+
addIntelligentTestRunnerSpanTags,
|
|
14
|
+
TEST_ITR_UNSKIPPABLE,
|
|
15
|
+
TEST_ITR_FORCED_RUN
|
|
14
16
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
15
17
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
16
18
|
const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
|
|
@@ -29,7 +31,9 @@ class CucumberPlugin extends CiPlugin {
|
|
|
29
31
|
status,
|
|
30
32
|
isSuitesSkipped,
|
|
31
33
|
numSkippedSuites,
|
|
32
|
-
testCodeCoverageLinesTotal
|
|
34
|
+
testCodeCoverageLinesTotal,
|
|
35
|
+
hasUnskippableSuites,
|
|
36
|
+
hasForcedToRunSuites
|
|
33
37
|
}) => {
|
|
34
38
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
|
|
35
39
|
addIntelligentTestRunnerSpanTags(
|
|
@@ -41,7 +45,9 @@ class CucumberPlugin extends CiPlugin {
|
|
|
41
45
|
isCodeCoverageEnabled,
|
|
42
46
|
testCodeCoverageLinesTotal,
|
|
43
47
|
skippingCount: numSkippedSuites,
|
|
44
|
-
skippingType: 'suite'
|
|
48
|
+
skippingType: 'suite',
|
|
49
|
+
hasUnskippableSuites,
|
|
50
|
+
hasForcedToRunSuites
|
|
45
51
|
}
|
|
46
52
|
)
|
|
47
53
|
|
|
@@ -55,13 +61,19 @@ class CucumberPlugin extends CiPlugin {
|
|
|
55
61
|
this.tracer._exporter.flush()
|
|
56
62
|
})
|
|
57
63
|
|
|
58
|
-
this.addSub('ci:cucumber:test-suite:start', (
|
|
64
|
+
this.addSub('ci:cucumber:test-suite:start', ({ testSuitePath, isUnskippable, isForcedToRun }) => {
|
|
59
65
|
const testSuiteMetadata = getTestSuiteCommonTags(
|
|
60
66
|
this.command,
|
|
61
67
|
this.frameworkVersion,
|
|
62
|
-
|
|
68
|
+
testSuitePath,
|
|
63
69
|
'cucumber'
|
|
64
70
|
)
|
|
71
|
+
if (isUnskippable) {
|
|
72
|
+
testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
73
|
+
}
|
|
74
|
+
if (isForcedToRun) {
|
|
75
|
+
testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
76
|
+
}
|
|
65
77
|
this.testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
|
|
66
78
|
childOf: this.testModuleSpan,
|
|
67
79
|
tags: {
|
|
@@ -21,11 +21,14 @@ const {
|
|
|
21
21
|
getCoveredFilenamesFromCoverage,
|
|
22
22
|
getTestSuitePath,
|
|
23
23
|
addIntelligentTestRunnerSpanTags,
|
|
24
|
-
TEST_SKIPPED_BY_ITR
|
|
24
|
+
TEST_SKIPPED_BY_ITR,
|
|
25
|
+
TEST_ITR_UNSKIPPABLE,
|
|
26
|
+
TEST_ITR_FORCED_RUN
|
|
25
27
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
26
28
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
27
29
|
const log = require('../../dd-trace/src/log')
|
|
28
30
|
const NoopTracer = require('../../dd-trace/src/noop/tracer')
|
|
31
|
+
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
29
32
|
|
|
30
33
|
const TEST_FRAMEWORK_NAME = 'cypress'
|
|
31
34
|
|
|
@@ -185,8 +188,11 @@ module.exports = (on, config) => {
|
|
|
185
188
|
let isSuitesSkippingEnabled = false
|
|
186
189
|
let isCodeCoverageEnabled = false
|
|
187
190
|
let testsToSkip = []
|
|
191
|
+
const unskippableSuites = []
|
|
192
|
+
let hasForcedToRunSuites = false
|
|
193
|
+
let hasUnskippableSuites = false
|
|
188
194
|
|
|
189
|
-
function getTestSpan (testName, testSuite) {
|
|
195
|
+
function getTestSpan (testName, testSuite, isUnskippable, isForcedToRun) {
|
|
190
196
|
const testSuiteTags = {
|
|
191
197
|
[TEST_COMMAND]: command,
|
|
192
198
|
[TEST_COMMAND]: command,
|
|
@@ -212,6 +218,16 @@ module.exports = (on, config) => {
|
|
|
212
218
|
testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
213
219
|
}
|
|
214
220
|
|
|
221
|
+
if (isUnskippable) {
|
|
222
|
+
hasUnskippableSuites = true
|
|
223
|
+
testSpanMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (isForcedToRun) {
|
|
227
|
+
hasForcedToRunSuites = true
|
|
228
|
+
testSpanMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
229
|
+
}
|
|
230
|
+
|
|
215
231
|
return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
|
|
216
232
|
childOf,
|
|
217
233
|
tags: {
|
|
@@ -233,13 +249,21 @@ module.exports = (on, config) => {
|
|
|
233
249
|
isCodeCoverageEnabled = itrConfig.isCodeCoverageEnabled
|
|
234
250
|
}
|
|
235
251
|
|
|
236
|
-
getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration).then(({ err, skippableTests }) => {
|
|
252
|
+
return getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration).then(({ err, skippableTests }) => {
|
|
237
253
|
if (err) {
|
|
238
254
|
log.error(err)
|
|
239
255
|
} else {
|
|
240
256
|
testsToSkip = skippableTests || []
|
|
241
257
|
}
|
|
242
258
|
|
|
259
|
+
// `details.specs` are test files
|
|
260
|
+
details.specs.forEach(({ absolute, relative }) => {
|
|
261
|
+
const isUnskippableSuite = isMarkedAsUnskippable({ path: absolute })
|
|
262
|
+
if (isUnskippableSuite) {
|
|
263
|
+
unskippableSuites.push(relative)
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
243
267
|
const childOf = getTestParentSpan(tracer)
|
|
244
268
|
rootDir = getRootDir(details)
|
|
245
269
|
|
|
@@ -340,7 +364,9 @@ module.exports = (on, config) => {
|
|
|
340
364
|
isSuitesSkippingEnabled,
|
|
341
365
|
isCodeCoverageEnabled,
|
|
342
366
|
skippingType: 'test',
|
|
343
|
-
skippingCount: skippedTests.length
|
|
367
|
+
skippingCount: skippedTests.length,
|
|
368
|
+
hasForcedToRunSuites,
|
|
369
|
+
hasUnskippableSuites
|
|
344
370
|
}
|
|
345
371
|
)
|
|
346
372
|
|
|
@@ -384,17 +410,21 @@ module.exports = (on, config) => {
|
|
|
384
410
|
},
|
|
385
411
|
'dd:beforeEach': (test) => {
|
|
386
412
|
const { testName, testSuite } = test
|
|
387
|
-
|
|
388
|
-
if (testsToSkip.find(test => {
|
|
413
|
+
const shouldSkip = !!testsToSkip.find(test => {
|
|
389
414
|
return testName === test.name && testSuite === test.suite
|
|
390
|
-
})
|
|
415
|
+
})
|
|
416
|
+
const isUnskippable = unskippableSuites.includes(testSuite)
|
|
417
|
+
const isForcedToRun = shouldSkip && isUnskippable
|
|
418
|
+
|
|
419
|
+
// skip test
|
|
420
|
+
if (shouldSkip && !isUnskippable) {
|
|
391
421
|
skippedTests.push(test)
|
|
392
422
|
isTestsSkipped = true
|
|
393
423
|
return { shouldSkip: true }
|
|
394
424
|
}
|
|
395
425
|
|
|
396
426
|
if (!activeSpan) {
|
|
397
|
-
activeSpan = getTestSpan(testName, testSuite)
|
|
427
|
+
activeSpan = getTestSpan(testName, testSuite, isUnskippable, isForcedToRun)
|
|
398
428
|
}
|
|
399
429
|
|
|
400
430
|
return activeSpan ? { traceId: activeSpan.context().toTraceId() } : {}
|
|
@@ -10,7 +10,9 @@ const {
|
|
|
10
10
|
TEST_PARAMETERS,
|
|
11
11
|
TEST_COMMAND,
|
|
12
12
|
TEST_FRAMEWORK_VERSION,
|
|
13
|
-
TEST_SOURCE_START
|
|
13
|
+
TEST_SOURCE_START,
|
|
14
|
+
TEST_ITR_UNSKIPPABLE,
|
|
15
|
+
TEST_ITR_FORCED_RUN
|
|
14
16
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
15
17
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
16
18
|
const id = require('../../dd-trace/src/id')
|
|
@@ -50,7 +52,9 @@ class JestPlugin extends CiPlugin {
|
|
|
50
52
|
isSuitesSkippingEnabled,
|
|
51
53
|
isCodeCoverageEnabled,
|
|
52
54
|
testCodeCoverageLinesTotal,
|
|
53
|
-
numSkippedSuites
|
|
55
|
+
numSkippedSuites,
|
|
56
|
+
hasUnskippableSuites,
|
|
57
|
+
hasForcedToRunSuites
|
|
54
58
|
}) => {
|
|
55
59
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
56
60
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
@@ -64,7 +68,9 @@ class JestPlugin extends CiPlugin {
|
|
|
64
68
|
isCodeCoverageEnabled,
|
|
65
69
|
testCodeCoverageLinesTotal,
|
|
66
70
|
skippingType: 'suite',
|
|
67
|
-
skippingCount: numSkippedSuites
|
|
71
|
+
skippingCount: numSkippedSuites,
|
|
72
|
+
hasUnskippableSuites,
|
|
73
|
+
hasForcedToRunSuites
|
|
68
74
|
}
|
|
69
75
|
)
|
|
70
76
|
|
|
@@ -89,7 +95,9 @@ class JestPlugin extends CiPlugin {
|
|
|
89
95
|
const {
|
|
90
96
|
_ddTestSessionId: testSessionId,
|
|
91
97
|
_ddTestCommand: testCommand,
|
|
92
|
-
_ddTestModuleId: testModuleId
|
|
98
|
+
_ddTestModuleId: testModuleId,
|
|
99
|
+
_ddForcedToRun,
|
|
100
|
+
_ddUnskippable
|
|
93
101
|
} = testEnvironmentOptions
|
|
94
102
|
|
|
95
103
|
const testSessionSpanContext = this.tracer.extract('text_map', {
|
|
@@ -99,6 +107,13 @@ class JestPlugin extends CiPlugin {
|
|
|
99
107
|
|
|
100
108
|
const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
|
|
101
109
|
|
|
110
|
+
if (_ddUnskippable) {
|
|
111
|
+
testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
112
|
+
if (_ddForcedToRun) {
|
|
113
|
+
testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
102
117
|
this.testSuiteSpan = this.tracer.startSpan('jest.test_suite', {
|
|
103
118
|
childOf: testSessionSpanContext,
|
|
104
119
|
tags: {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
const { readFileSync } = require('fs')
|
|
2
|
+
const { parse, extract } = require('jest-docblock')
|
|
3
|
+
|
|
1
4
|
const { getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
|
|
5
|
+
const log = require('../../dd-trace/src/log')
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* There are two ways to call `test.each` in `jest`:
|
|
@@ -47,17 +51,56 @@ function getJestTestName (test) {
|
|
|
47
51
|
return titles.join(' ')
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
function isMarkedAsUnskippable (test) {
|
|
55
|
+
let docblocks
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const testSource = readFileSync(test.path, 'utf8')
|
|
59
|
+
docblocks = parse(extract(testSource))
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// If we have issues parsing the file, we'll assume no unskippable was passed
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// docblocks were correctly parsed but it does not include a @datadog block
|
|
66
|
+
if (!docblocks?.datadog) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(docblocks.datadog).unskippable
|
|
72
|
+
} catch (e) {
|
|
73
|
+
// If the @datadog block comment is present but malformed, we'll run the suite
|
|
74
|
+
log.warn('@datadog block comment is malformed.')
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
|
|
51
80
|
return originalTests.reduce((acc, test) => {
|
|
52
81
|
const relativePath = getTestSuitePath(test.path, rootDir)
|
|
53
82
|
const shouldBeSkipped = skippableSuites.includes(relativePath)
|
|
83
|
+
|
|
84
|
+
if (isMarkedAsUnskippable(test)) {
|
|
85
|
+
acc.suitesToRun.push(test)
|
|
86
|
+
if (test?.context?.config?.testEnvironmentOptions) {
|
|
87
|
+
test.context.config.testEnvironmentOptions['_ddUnskippable'] = true
|
|
88
|
+
acc.hasUnskippableSuites = true
|
|
89
|
+
if (shouldBeSkipped) {
|
|
90
|
+
test.context.config.testEnvironmentOptions['_ddForcedToRun'] = true
|
|
91
|
+
acc.hasForcedToRunSuites = true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return acc
|
|
95
|
+
}
|
|
96
|
+
|
|
54
97
|
if (shouldBeSkipped) {
|
|
55
98
|
acc.skippedSuites.push(relativePath)
|
|
56
99
|
} else {
|
|
57
100
|
acc.suitesToRun.push(test)
|
|
58
101
|
}
|
|
59
102
|
return acc
|
|
60
|
-
}, { skippedSuites: [], suitesToRun: [] })
|
|
103
|
+
}, { skippedSuites: [], suitesToRun: [], hasUnskippableSuites: false, hasForcedToRunSuites: false })
|
|
61
104
|
}
|
|
62
105
|
|
|
63
|
-
module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun }
|
|
106
|
+
module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun, isMarkedAsUnskippable }
|
|
@@ -9,15 +9,20 @@ class MemcachedPlugin extends CachePlugin {
|
|
|
9
9
|
start ({ client, server, query }) {
|
|
10
10
|
const address = getAddress(client, server, query)
|
|
11
11
|
|
|
12
|
+
const meta = {
|
|
13
|
+
'out.host': address[0],
|
|
14
|
+
[CLIENT_PORT_KEY]: address[1]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (this.config.memcachedCommandEnabled) {
|
|
18
|
+
meta['memcached.command'] = query.command
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
this.startSpan({
|
|
13
22
|
service: this.serviceName({ pluginConfig: this.config, system: this.system }),
|
|
14
23
|
resource: query.type,
|
|
15
24
|
type: 'memcached',
|
|
16
|
-
meta
|
|
17
|
-
'memcached.command': query.command,
|
|
18
|
-
'out.host': address[0],
|
|
19
|
-
[CLIENT_PORT_KEY]: address[1]
|
|
20
|
-
}
|
|
25
|
+
meta
|
|
21
26
|
})
|
|
22
27
|
}
|
|
23
28
|
}
|
|
@@ -11,7 +11,9 @@ const {
|
|
|
11
11
|
getTestParametersString,
|
|
12
12
|
getTestSuiteCommonTags,
|
|
13
13
|
addIntelligentTestRunnerSpanTags,
|
|
14
|
-
TEST_SOURCE_START
|
|
14
|
+
TEST_SOURCE_START,
|
|
15
|
+
TEST_ITR_UNSKIPPABLE,
|
|
16
|
+
TEST_ITR_FORCED_RUN
|
|
15
17
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
16
18
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
17
19
|
|
|
@@ -47,14 +49,21 @@ class MochaPlugin extends CiPlugin {
|
|
|
47
49
|
this.tracer._exporter.exportCoverage(formattedCoverage)
|
|
48
50
|
})
|
|
49
51
|
|
|
50
|
-
this.addSub('ci:mocha:test-suite:start', (
|
|
52
|
+
this.addSub('ci:mocha:test-suite:start', ({ testSuite, isUnskippable, isForcedToRun }) => {
|
|
51
53
|
const store = storage.getStore()
|
|
52
54
|
const testSuiteMetadata = getTestSuiteCommonTags(
|
|
53
55
|
this.command,
|
|
54
56
|
this.frameworkVersion,
|
|
55
|
-
getTestSuitePath(
|
|
57
|
+
getTestSuitePath(testSuite, this.sourceRoot),
|
|
56
58
|
'mocha'
|
|
57
59
|
)
|
|
60
|
+
if (isUnskippable) {
|
|
61
|
+
testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
62
|
+
}
|
|
63
|
+
if (isForcedToRun) {
|
|
64
|
+
testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
const testSuiteSpan = this.tracer.startSpan('mocha.test_suite', {
|
|
59
68
|
childOf: this.testModuleSpan,
|
|
60
69
|
tags: {
|
|
@@ -64,7 +73,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
64
73
|
}
|
|
65
74
|
})
|
|
66
75
|
this.enter(testSuiteSpan, store)
|
|
67
|
-
this._testSuites.set(
|
|
76
|
+
this._testSuites.set(testSuite, testSuiteSpan)
|
|
68
77
|
})
|
|
69
78
|
|
|
70
79
|
this.addSub('ci:mocha:test-suite:finish', (status) => {
|
|
@@ -139,7 +148,9 @@ class MochaPlugin extends CiPlugin {
|
|
|
139
148
|
status,
|
|
140
149
|
isSuitesSkipped,
|
|
141
150
|
testCodeCoverageLinesTotal,
|
|
142
|
-
numSkippedSuites
|
|
151
|
+
numSkippedSuites,
|
|
152
|
+
hasForcedToRunSuites,
|
|
153
|
+
hasUnskippableSuites
|
|
143
154
|
}) => {
|
|
144
155
|
if (this.testSessionSpan) {
|
|
145
156
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
|
|
@@ -155,7 +166,9 @@ class MochaPlugin extends CiPlugin {
|
|
|
155
166
|
isCodeCoverageEnabled,
|
|
156
167
|
testCodeCoverageLinesTotal,
|
|
157
168
|
skippingCount: numSkippedSuites,
|
|
158
|
-
skippingType: 'suite'
|
|
169
|
+
skippingType: 'suite',
|
|
170
|
+
hasForcedToRunSuites,
|
|
171
|
+
hasUnskippableSuites
|
|
159
172
|
}
|
|
160
173
|
)
|
|
161
174
|
|
|
@@ -11,5 +11,7 @@ module.exports = {
|
|
|
11
11
|
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
|
|
12
12
|
passportVerify: dc.channel('datadog:passport:verify:finish'),
|
|
13
13
|
queryParser: dc.channel('datadog:query:read:finish'),
|
|
14
|
-
setCookieChannel: dc.channel('datadog:iast:set-cookie')
|
|
14
|
+
setCookieChannel: dc.channel('datadog:iast:set-cookie'),
|
|
15
|
+
nextBodyParsed: dc.channel('apm:next:body-parsed'),
|
|
16
|
+
nextQueryParsed: dc.channel('apm:next:query-parsed')
|
|
15
17
|
}
|
|
@@ -7,6 +7,7 @@ module.exports = {
|
|
|
7
7
|
'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
|
|
8
8
|
'NO_HTTPONLY_COOKIE_ANALYZER': require('./no-httponly-cookie-analyzer'),
|
|
9
9
|
'NO_SAMESITE_COOKIE_ANALYZER': require('./no-samesite-cookie-analyzer'),
|
|
10
|
+
'NOSQL_MONGODB_INJECTION': require('./nosql-injection-mongodb-analyzer'),
|
|
10
11
|
'PATH_TRAVERSAL_ANALYZER': require('./path-traversal-analyzer'),
|
|
11
12
|
'SQL_INJECTION_ANALYZER': require('./sql-injection-analyzer'),
|
|
12
13
|
'SSRF': require('./ssrf-analyzer'),
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { NOSQL_MONGODB_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
const { getRanges, addSecureMark } = require('../taint-tracking/operations')
|
|
6
|
+
const { getNodeModulesPaths } = require('../path-line')
|
|
7
|
+
const { getNextSecureMark } = require('../taint-tracking/secure-marks-generator')
|
|
8
|
+
const { storage } = require('../../../../../datadog-core')
|
|
9
|
+
const { getIastContext } = require('../iast-context')
|
|
10
|
+
|
|
11
|
+
const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose')
|
|
12
|
+
const MONGODB_NOSQL_SECURE_MARK = getNextSecureMark()
|
|
13
|
+
|
|
14
|
+
function iterateObjectStrings (target, fn, levelKeys = [], depth = 50, visited = new Set()) {
|
|
15
|
+
if (target && typeof target === 'object') {
|
|
16
|
+
Object.keys(target).forEach((key) => {
|
|
17
|
+
const nextLevelKeys = [...levelKeys, key]
|
|
18
|
+
const val = target[key]
|
|
19
|
+
|
|
20
|
+
if (typeof val === 'string') {
|
|
21
|
+
fn(val, nextLevelKeys, target, key)
|
|
22
|
+
} else if (depth > 0 && !visited.has(val)) {
|
|
23
|
+
iterateObjectStrings(val, fn, nextLevelKeys, depth - 1, visited)
|
|
24
|
+
visited.add(val)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
31
|
+
constructor () {
|
|
32
|
+
super(NOSQL_MONGODB_INJECTION)
|
|
33
|
+
this.sanitizedObjects = new WeakSet()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onConfigure () {
|
|
37
|
+
this.configureSanitizers()
|
|
38
|
+
|
|
39
|
+
this.addSub('datadog:mongodb:collection:filter:start', ({ filters }) => {
|
|
40
|
+
const store = storage.getStore()
|
|
41
|
+
if (store && !store.nosqlAnalyzed && filters?.length) {
|
|
42
|
+
filters.forEach(filter => {
|
|
43
|
+
this.analyze({ filter }, store)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
this.addSub('datadog:mongoose:model:filter:start', ({ filters }) => {
|
|
49
|
+
const store = storage.getStore()
|
|
50
|
+
if (!store) return
|
|
51
|
+
|
|
52
|
+
if (filters?.length) {
|
|
53
|
+
filters.forEach(filter => {
|
|
54
|
+
this.analyze({ filter }, store)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
storage.enterWith({ ...store, nosqlAnalyzed: true, mongooseParentStore: store })
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
this.addSub('datadog:mongoose:model:filter:finish', () => {
|
|
62
|
+
const store = storage.getStore()
|
|
63
|
+
if (store?.mongooseParentStore) {
|
|
64
|
+
storage.enterWith(store.mongooseParentStore)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
configureSanitizers () {
|
|
70
|
+
this.addNotSinkSub('datadog:express-mongo-sanitize:filter:finish', ({ sanitizedProperties, req }) => {
|
|
71
|
+
const store = storage.getStore()
|
|
72
|
+
const iastContext = getIastContext(store)
|
|
73
|
+
|
|
74
|
+
if (iastContext) { // do nothing if we are not in an iast request
|
|
75
|
+
sanitizedProperties.forEach(key => {
|
|
76
|
+
iterateObjectStrings(req[key], function (value, levelKeys) {
|
|
77
|
+
if (typeof value === 'string') {
|
|
78
|
+
let parentObj = req[key]
|
|
79
|
+
const levelsLength = levelKeys.length
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < levelsLength; i++) {
|
|
82
|
+
const currentLevelKey = levelKeys[i]
|
|
83
|
+
|
|
84
|
+
if (i === levelsLength - 1) {
|
|
85
|
+
parentObj[currentLevelKey] = addSecureMark(iastContext, value, MONGODB_NOSQL_SECURE_MARK)
|
|
86
|
+
} else {
|
|
87
|
+
parentObj = parentObj[currentLevelKey]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
this.addNotSinkSub('datadog:express-mongo-sanitize:sanitize:finish', ({ sanitizedObject }) => {
|
|
97
|
+
const store = storage.getStore()
|
|
98
|
+
const iastContext = getIastContext(store)
|
|
99
|
+
|
|
100
|
+
if (iastContext) { // do nothing if we are not in an iast request
|
|
101
|
+
iterateObjectStrings(sanitizedObject, function (value, levelKeys, parent, lastKey) {
|
|
102
|
+
try {
|
|
103
|
+
parent[lastKey] = addSecureMark(iastContext, value, MONGODB_NOSQL_SECURE_MARK)
|
|
104
|
+
} catch {
|
|
105
|
+
// if it is a readonly property, do nothing
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
this.addNotSinkSub('datadog:mongoose:sanitize-filter:finish', ({ sanitizedObject }) => {
|
|
112
|
+
this.sanitizedObjects.add(sanitizedObject)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_isVulnerable (value, iastContext) {
|
|
117
|
+
if (value?.filter && iastContext) {
|
|
118
|
+
let isVulnerable = false
|
|
119
|
+
|
|
120
|
+
if (this.sanitizedObjects.has(value.filter)) {
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const rangesByKey = {}
|
|
125
|
+
const allRanges = []
|
|
126
|
+
|
|
127
|
+
iterateObjectStrings(value.filter, function (val, nextLevelKeys) {
|
|
128
|
+
const ranges = getRanges(iastContext, val)
|
|
129
|
+
if (ranges?.length) {
|
|
130
|
+
const filteredRanges = []
|
|
131
|
+
|
|
132
|
+
for (const range of ranges) {
|
|
133
|
+
if ((range.secureMarks & MONGODB_NOSQL_SECURE_MARK) !== MONGODB_NOSQL_SECURE_MARK) {
|
|
134
|
+
isVulnerable = true
|
|
135
|
+
filteredRanges.push(range)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (filteredRanges.length > 0) {
|
|
140
|
+
rangesByKey[nextLevelKeys.join('.')] = filteredRanges
|
|
141
|
+
allRanges.push(...filteredRanges)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}, [], 4)
|
|
145
|
+
|
|
146
|
+
if (isVulnerable) {
|
|
147
|
+
value.rangesToApply = rangesByKey
|
|
148
|
+
value.ranges = allRanges
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return isVulnerable
|
|
152
|
+
}
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
_getEvidence (value, iastContext) {
|
|
157
|
+
return { value: value.filter, rangesToApply: value.rangesToApply, ranges: value.ranges }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_getExcludedPaths () {
|
|
161
|
+
return EXCLUDED_PATHS_FROM_STACK
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = new NosqlInjectionMongodbAnalyzer()
|
|
166
|
+
module.exports.MONGODB_NOSQL_SECURE_MARK = MONGODB_NOSQL_SECURE_MARK
|
|
@@ -8,7 +8,7 @@ const { getIastContext } = require('../iast-context')
|
|
|
8
8
|
const { addVulnerability } = require('../vulnerability-reporter')
|
|
9
9
|
const { getNodeModulesPaths } = require('../path-line')
|
|
10
10
|
|
|
11
|
-
const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool')
|
|
11
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool', 'knex')
|
|
12
12
|
|
|
13
13
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
14
14
|
constructor () {
|
|
@@ -31,6 +31,12 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
31
31
|
|
|
32
32
|
this.addSub('datadog:mysql:pool:query:start', ({ sql }) => this.getStoreAndAnalyze(sql, 'MYSQL'))
|
|
33
33
|
this.addSub('datadog:mysql:pool:query:finish', () => this.returnToParentStore())
|
|
34
|
+
|
|
35
|
+
this.addSub('datadog:knex:raw:start', ({ sql, dialect: knexDialect }) => {
|
|
36
|
+
const dialect = this.normalizeKnexDialect(knexDialect)
|
|
37
|
+
this.getStoreAndAnalyze(sql, dialect)
|
|
38
|
+
})
|
|
39
|
+
this.addSub('datadog:knex:raw:finish', () => this.returnToParentStore())
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
getStoreAndAnalyze (query, dialect) {
|
|
@@ -83,6 +89,20 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
83
89
|
_getExcludedPaths () {
|
|
84
90
|
return EXCLUDED_PATHS
|
|
85
91
|
}
|
|
92
|
+
|
|
93
|
+
normalizeKnexDialect (knexDialect) {
|
|
94
|
+
if (knexDialect === 'postgresql') {
|
|
95
|
+
return 'POSTGRES'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (knexDialect === 'sqlite3') {
|
|
99
|
+
return 'SQLITE'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (typeof knexDialect === 'string') {
|
|
103
|
+
return knexDialect.toUpperCase()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
module.exports = new SqlInjectionAnalyzer()
|
|
@@ -6,8 +6,8 @@ const { getNodeModulesPaths } = require('../path-line')
|
|
|
6
6
|
const { getRanges } = require('../taint-tracking/operations')
|
|
7
7
|
const {
|
|
8
8
|
HTTP_REQUEST_HEADER_VALUE,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
HTTP_REQUEST_PATH_PARAM,
|
|
10
|
+
HTTP_REQUEST_URI
|
|
11
11
|
} = require('../taint-tracking/source-types')
|
|
12
12
|
|
|
13
13
|
const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
|
|
@@ -56,7 +56,7 @@ class UnvalidatedRedirectAnalyzer extends InjectionAnalyzer {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
_isUrl (range) {
|
|
59
|
-
return range.iinfo.type ===
|
|
59
|
+
return range.iinfo.type === HTTP_REQUEST_URI
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
_getExcludedPaths () {
|
|
@@ -71,8 +71,7 @@ class Analyzer extends SinkIastPlugin {
|
|
|
71
71
|
return store && !iastContext
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
analyze (value) {
|
|
75
|
-
const store = storage.getStore()
|
|
74
|
+
analyze (value, store = storage.getStore()) {
|
|
76
75
|
const iastContext = getIastContext(store)
|
|
77
76
|
if (this._isInvalidContext(store, iastContext)) return
|
|
78
77
|
|
|
@@ -196,6 +196,10 @@ class SinkIastPlugin extends IastPlugin {
|
|
|
196
196
|
addSub (iastPluginSub, handler) {
|
|
197
197
|
return super.addSub({ tagKey: TagKey.VULNERABILITY_TYPE, ...iastPluginSub }, handler)
|
|
198
198
|
}
|
|
199
|
+
|
|
200
|
+
addNotSinkSub (iastPluginSub, handler) {
|
|
201
|
+
return super.addSub(iastPluginSub, handler)
|
|
202
|
+
}
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
module.exports = {
|