dd-trace 5.36.0 → 5.37.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 +2 -1
- package/index.d.ts +5 -0
- package/loader-hook.mjs +0 -4
- package/package.json +14 -13
- package/packages/datadog-instrumentations/src/cucumber.js +54 -1
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +103 -5
- package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
- package/packages/datadog-instrumentations/src/mysql2.js +3 -3
- package/packages/datadog-instrumentations/src/openai.js +8 -0
- package/packages/datadog-instrumentations/src/playwright.js +70 -22
- package/packages/datadog-instrumentations/src/vitest.js +60 -6
- package/packages/datadog-plugin-cucumber/src/index.js +20 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +67 -7
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
- package/packages/datadog-plugin-graphql/src/utils.js +8 -1
- package/packages/datadog-plugin-jest/src/index.js +12 -2
- package/packages/datadog-plugin-mocha/src/index.js +22 -3
- package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
- package/packages/datadog-plugin-openai/src/tracing.js +1 -2
- package/packages/datadog-plugin-playwright/src/index.js +31 -5
- package/packages/datadog-plugin-vitest/src/index.js +25 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +11 -24
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +22 -2
- package/packages/dd-trace/src/appsec/iast/index.js +2 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
- package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -4
- package/packages/dd-trace/src/appsec/rasp/lfi.js +2 -2
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -49
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
- package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
- package/packages/dd-trace/src/config.js +16 -3
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
- package/packages/dd-trace/src/iitm.js +2 -2
- package/packages/dd-trace/src/llmobs/tagger.js +12 -2
- package/packages/dd-trace/src/opentracing/span.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +16 -3
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
- package/packages/dd-trace/src/plugins/util/test.js +6 -4
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/ritm.js +2 -1
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/memwatch/package.json +0 -9
|
@@ -28,7 +28,7 @@ module.exports = class DdTraceApiPlugin extends Plugin {
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
const handleEvent = (name) => {
|
|
31
|
-
const counter = apiMetrics.count('
|
|
31
|
+
const counter = apiMetrics.count('public_api.called', [
|
|
32
32
|
`name:${name.replaceAll(':', '.')}`,
|
|
33
33
|
'api_version:v1',
|
|
34
34
|
injectionEnabledTag
|
|
@@ -74,8 +74,6 @@ module.exports = class DdTraceApiPlugin extends Plugin {
|
|
|
74
74
|
const proxyVal = proxy()
|
|
75
75
|
objectMap.set(proxyVal, ret.value)
|
|
76
76
|
ret.value = proxyVal
|
|
77
|
-
} else if (ret.value && typeof ret.value === 'object') {
|
|
78
|
-
throw new TypeError(`Objects need proxies when returned via API (${name})`)
|
|
79
77
|
}
|
|
80
78
|
} catch (e) {
|
|
81
79
|
ret.error = e
|
|
@@ -27,7 +27,14 @@ function extractErrorIntoSpanEvent (config, span, exc) {
|
|
|
27
27
|
if (config.graphqlErrorExtensions) {
|
|
28
28
|
for (const ext of config.graphqlErrorExtensions) {
|
|
29
29
|
if (exc.extensions?.[ext]) {
|
|
30
|
-
|
|
30
|
+
const value = exc.extensions[ext]
|
|
31
|
+
|
|
32
|
+
// We should only stringify the value if it is not of type number or boolean
|
|
33
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
34
|
+
attributes[`extensions.${ext}`] = value
|
|
35
|
+
} else {
|
|
36
|
+
attributes[`extensions.${ext}`] = String(value)
|
|
37
|
+
}
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
40
|
}
|
|
@@ -24,7 +24,9 @@ const {
|
|
|
24
24
|
TEST_IS_RUM_ACTIVE,
|
|
25
25
|
TEST_BROWSER_DRIVER,
|
|
26
26
|
getFormattedError,
|
|
27
|
-
TEST_RETRY_REASON
|
|
27
|
+
TEST_RETRY_REASON,
|
|
28
|
+
TEST_MANAGEMENT_ENABLED,
|
|
29
|
+
TEST_MANAGEMENT_IS_QUARANTINED
|
|
28
30
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
29
31
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
30
32
|
const id = require('../../dd-trace/src/id')
|
|
@@ -106,6 +108,7 @@ class JestPlugin extends CiPlugin {
|
|
|
106
108
|
error,
|
|
107
109
|
isEarlyFlakeDetectionEnabled,
|
|
108
110
|
isEarlyFlakeDetectionFaulty,
|
|
111
|
+
isQuarantinedTestsEnabled,
|
|
109
112
|
onDone
|
|
110
113
|
}) => {
|
|
111
114
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -137,6 +140,9 @@ class JestPlugin extends CiPlugin {
|
|
|
137
140
|
if (isEarlyFlakeDetectionFaulty) {
|
|
138
141
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
139
142
|
}
|
|
143
|
+
if (isQuarantinedTestsEnabled) {
|
|
144
|
+
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
145
|
+
}
|
|
140
146
|
|
|
141
147
|
this.testModuleSpan.finish()
|
|
142
148
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
@@ -166,6 +172,7 @@ class JestPlugin extends CiPlugin {
|
|
|
166
172
|
config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
|
|
167
173
|
config._ddRepositoryRoot = this.repositoryRoot
|
|
168
174
|
config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
|
|
175
|
+
config._ddIsQuarantinedTestsEnabled = this.libraryConfig?.isQuarantinedTestsEnabled ?? false
|
|
169
176
|
config._ddFlakyTestRetriesCount = this.libraryConfig?.flakyTestRetriesCount
|
|
170
177
|
config._ddIsDiEnabled = this.libraryConfig?.isDiEnabled ?? false
|
|
171
178
|
config._ddIsKnownTestsEnabled = this.libraryConfig?.isKnownTestsEnabled ?? false
|
|
@@ -325,12 +332,15 @@ class JestPlugin extends CiPlugin {
|
|
|
325
332
|
this.activeTestSpan = span
|
|
326
333
|
})
|
|
327
334
|
|
|
328
|
-
this.addSub('ci:jest:test:finish', ({ status, testStartLine }) => {
|
|
335
|
+
this.addSub('ci:jest:test:finish', ({ status, testStartLine, isQuarantined }) => {
|
|
329
336
|
const span = storage('legacy').getStore().span
|
|
330
337
|
span.setTag(TEST_STATUS, status)
|
|
331
338
|
if (testStartLine) {
|
|
332
339
|
span.setTag(TEST_SOURCE_START, testStartLine)
|
|
333
340
|
}
|
|
341
|
+
if (isQuarantined) {
|
|
342
|
+
span.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
|
|
343
|
+
}
|
|
334
344
|
|
|
335
345
|
const spanTags = span.context()._tags
|
|
336
346
|
this.telemetry.ciVisEvent(
|
|
@@ -31,7 +31,9 @@ const {
|
|
|
31
31
|
MOCHA_IS_PARALLEL,
|
|
32
32
|
TEST_IS_RUM_ACTIVE,
|
|
33
33
|
TEST_BROWSER_DRIVER,
|
|
34
|
-
TEST_RETRY_REASON
|
|
34
|
+
TEST_RETRY_REASON,
|
|
35
|
+
TEST_MANAGEMENT_ENABLED,
|
|
36
|
+
TEST_MANAGEMENT_IS_QUARANTINED
|
|
35
37
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
36
38
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
37
39
|
const {
|
|
@@ -48,6 +50,8 @@ const {
|
|
|
48
50
|
const id = require('../../dd-trace/src/id')
|
|
49
51
|
const log = require('../../dd-trace/src/log')
|
|
50
52
|
|
|
53
|
+
const BREAKPOINT_SET_GRACE_PERIOD_MS = 200
|
|
54
|
+
|
|
51
55
|
function getTestSuiteLevelVisibilityTags (testSuiteSpan) {
|
|
52
56
|
const testSuiteSpanContext = testSuiteSpan.context()
|
|
53
57
|
const suiteTags = {
|
|
@@ -279,7 +283,12 @@ class MochaPlugin extends CiPlugin {
|
|
|
279
283
|
this.runningTestProbe = { file, line }
|
|
280
284
|
this.testErrorStackIndex = stackIndex
|
|
281
285
|
test._ddShouldWaitForHitProbe = true
|
|
282
|
-
|
|
286
|
+
const waitUntil = Date.now() + BREAKPOINT_SET_GRACE_PERIOD_MS
|
|
287
|
+
while (Date.now() < waitUntil) {
|
|
288
|
+
// TODO: To avoid a race condition, we should wait until `probeInformation.setProbePromise` has resolved.
|
|
289
|
+
// However, Mocha doesn't have a mechanism for waiting asyncrounously here, so for now, we'll have to
|
|
290
|
+
// fall back to a fixed syncronous delay.
|
|
291
|
+
}
|
|
283
292
|
}
|
|
284
293
|
}
|
|
285
294
|
|
|
@@ -302,6 +311,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
302
311
|
error,
|
|
303
312
|
isEarlyFlakeDetectionEnabled,
|
|
304
313
|
isEarlyFlakeDetectionFaulty,
|
|
314
|
+
isQuarantinedTestsEnabled,
|
|
305
315
|
isParallel
|
|
306
316
|
}) => {
|
|
307
317
|
if (this.testSessionSpan) {
|
|
@@ -318,6 +328,10 @@ class MochaPlugin extends CiPlugin {
|
|
|
318
328
|
this.testSessionSpan.setTag(MOCHA_IS_PARALLEL, 'true')
|
|
319
329
|
}
|
|
320
330
|
|
|
331
|
+
if (isQuarantinedTestsEnabled) {
|
|
332
|
+
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
333
|
+
}
|
|
334
|
+
|
|
321
335
|
addIntelligentTestRunnerSpanTags(
|
|
322
336
|
this.testSessionSpan,
|
|
323
337
|
this.testModuleSpan,
|
|
@@ -390,7 +404,8 @@ class MochaPlugin extends CiPlugin {
|
|
|
390
404
|
isNew,
|
|
391
405
|
isEfdRetry,
|
|
392
406
|
testStartLine,
|
|
393
|
-
isParallel
|
|
407
|
+
isParallel,
|
|
408
|
+
isQuarantined
|
|
394
409
|
} = testInfo
|
|
395
410
|
|
|
396
411
|
const testName = removeEfdStringFromTestName(testInfo.testName)
|
|
@@ -409,6 +424,10 @@ class MochaPlugin extends CiPlugin {
|
|
|
409
424
|
extraTags[MOCHA_IS_PARALLEL] = 'true'
|
|
410
425
|
}
|
|
411
426
|
|
|
427
|
+
if (isQuarantined) {
|
|
428
|
+
extraTags[TEST_MANAGEMENT_IS_QUARANTINED] = 'true'
|
|
429
|
+
}
|
|
430
|
+
|
|
412
431
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
|
|
413
432
|
const testSuiteSpan = this._testSuites.get(testSuite)
|
|
414
433
|
|
|
@@ -11,8 +11,9 @@ class MongodbCorePlugin extends DatabasePlugin {
|
|
|
11
11
|
start ({ ns, ops, options = {}, name }) {
|
|
12
12
|
const query = getQuery(ops)
|
|
13
13
|
const resource = truncate(getResource(this, ns, query, name))
|
|
14
|
-
this.
|
|
15
|
-
|
|
14
|
+
const service = this.serviceName({ pluginConfig: this.config })
|
|
15
|
+
const span = this.startSpan(this.operationName(), {
|
|
16
|
+
service,
|
|
16
17
|
resource,
|
|
17
18
|
type: 'mongodb',
|
|
18
19
|
kind: 'client',
|
|
@@ -24,6 +25,7 @@ class MongodbCorePlugin extends DatabasePlugin {
|
|
|
24
25
|
'out.port': options.port
|
|
25
26
|
}
|
|
26
27
|
})
|
|
28
|
+
ops = this.injectDbmCommand(span, ops, service)
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
getPeerService (tags) {
|
|
@@ -34,6 +36,30 @@ class MongodbCorePlugin extends DatabasePlugin {
|
|
|
34
36
|
}
|
|
35
37
|
return super.getPeerService(tags)
|
|
36
38
|
}
|
|
39
|
+
|
|
40
|
+
injectDbmCommand (span, command, serviceName) {
|
|
41
|
+
const dbmTraceComment = this.createDbmComment(span, serviceName)
|
|
42
|
+
|
|
43
|
+
if (!dbmTraceComment) {
|
|
44
|
+
return command
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// create a copy of the command to avoid mutating the original
|
|
48
|
+
const dbmTracedCommand = { ...command }
|
|
49
|
+
|
|
50
|
+
if (dbmTracedCommand.comment) {
|
|
51
|
+
// if the command already has a comment, append the dbm trace comment
|
|
52
|
+
if (typeof dbmTracedCommand.comment === 'string') {
|
|
53
|
+
dbmTracedCommand.comment += `,${dbmTraceComment}`
|
|
54
|
+
} else if (Array.isArray(dbmTracedCommand.comment)) {
|
|
55
|
+
dbmTracedCommand.comment.push(dbmTraceComment)
|
|
56
|
+
} // do nothing if the comment is not a string or an array
|
|
57
|
+
} else {
|
|
58
|
+
dbmTracedCommand.comment = dbmTraceComment
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return dbmTracedCommand
|
|
62
|
+
}
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
function sanitizeBigInt (data) {
|
|
@@ -15,7 +15,6 @@ let normalize
|
|
|
15
15
|
|
|
16
16
|
function safeRequire (path) {
|
|
17
17
|
try {
|
|
18
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
19
18
|
return require(path)
|
|
20
19
|
} catch {
|
|
21
20
|
return null
|
|
@@ -796,7 +795,7 @@ function truncateApiKey (apiKey) {
|
|
|
796
795
|
|
|
797
796
|
function tagChatCompletionRequestContent (contents, messageIdx, tags) {
|
|
798
797
|
if (typeof contents === 'string') {
|
|
799
|
-
tags[`openai.request.messages.${messageIdx}.content`] = contents
|
|
798
|
+
tags[`openai.request.messages.${messageIdx}.content`] = normalize(contents)
|
|
800
799
|
} else if (Array.isArray(contents)) {
|
|
801
800
|
// content can also be an array of objects
|
|
802
801
|
// which represent text input or image url
|
|
@@ -11,12 +11,15 @@ const {
|
|
|
11
11
|
TEST_SOURCE_START,
|
|
12
12
|
TEST_CODE_OWNERS,
|
|
13
13
|
TEST_SOURCE_FILE,
|
|
14
|
-
|
|
14
|
+
TEST_PARAMETERS,
|
|
15
15
|
TEST_IS_NEW,
|
|
16
16
|
TEST_IS_RETRY,
|
|
17
17
|
TEST_EARLY_FLAKE_ENABLED,
|
|
18
18
|
TELEMETRY_TEST_SESSION,
|
|
19
|
-
TEST_RETRY_REASON
|
|
19
|
+
TEST_RETRY_REASON,
|
|
20
|
+
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
21
|
+
TEST_MANAGEMENT_ENABLED,
|
|
22
|
+
TEST_BROWSER_NAME
|
|
20
23
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
21
24
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
22
25
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -38,7 +41,12 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
38
41
|
this.numFailedTests = 0
|
|
39
42
|
this.numFailedSuites = 0
|
|
40
43
|
|
|
41
|
-
this.addSub('ci:playwright:session:finish', ({
|
|
44
|
+
this.addSub('ci:playwright:session:finish', ({
|
|
45
|
+
status,
|
|
46
|
+
isEarlyFlakeDetectionEnabled,
|
|
47
|
+
isQuarantinedTestsEnabled,
|
|
48
|
+
onDone
|
|
49
|
+
}) => {
|
|
42
50
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
43
51
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
44
52
|
|
|
@@ -56,6 +64,10 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
56
64
|
this.testSessionSpan.setTag('error', error)
|
|
57
65
|
}
|
|
58
66
|
|
|
67
|
+
if (isQuarantinedTestsEnabled) {
|
|
68
|
+
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
this.testModuleSpan.finish()
|
|
60
72
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
61
73
|
this.testSessionSpan.finish()
|
|
@@ -128,7 +140,16 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
128
140
|
|
|
129
141
|
this.enter(span, store)
|
|
130
142
|
})
|
|
131
|
-
this.addSub('ci:playwright:test:finish', ({
|
|
143
|
+
this.addSub('ci:playwright:test:finish', ({
|
|
144
|
+
testStatus,
|
|
145
|
+
steps,
|
|
146
|
+
error,
|
|
147
|
+
extraTags,
|
|
148
|
+
isNew,
|
|
149
|
+
isEfdRetry,
|
|
150
|
+
isRetry,
|
|
151
|
+
isQuarantined
|
|
152
|
+
}) => {
|
|
132
153
|
const store = storage('legacy').getStore()
|
|
133
154
|
const span = store && store.span
|
|
134
155
|
if (!span) return
|
|
@@ -151,6 +172,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
151
172
|
if (isRetry) {
|
|
152
173
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
153
174
|
}
|
|
175
|
+
if (isQuarantined) {
|
|
176
|
+
span.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
|
|
177
|
+
}
|
|
154
178
|
|
|
155
179
|
steps.forEach(step => {
|
|
156
180
|
const stepStartTime = step.startTime.getTime()
|
|
@@ -202,7 +226,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
202
226
|
extraTags[TEST_SOURCE_FILE] = testSourceFile || testSuite
|
|
203
227
|
}
|
|
204
228
|
if (browserName) {
|
|
205
|
-
|
|
229
|
+
// Added as parameter too because it should affect the test fingerprint
|
|
230
|
+
extraTags[TEST_PARAMETERS] = JSON.stringify({ arguments: { browser: browserName }, metadata: {} })
|
|
231
|
+
extraTags[TEST_BROWSER_NAME] = browserName
|
|
206
232
|
}
|
|
207
233
|
|
|
208
234
|
return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
|
|
@@ -18,7 +18,9 @@ const {
|
|
|
18
18
|
TEST_IS_NEW,
|
|
19
19
|
TEST_EARLY_FLAKE_ENABLED,
|
|
20
20
|
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
21
|
-
TEST_RETRY_REASON
|
|
21
|
+
TEST_RETRY_REASON,
|
|
22
|
+
TEST_MANAGEMENT_ENABLED,
|
|
23
|
+
TEST_MANAGEMENT_IS_QUARANTINED
|
|
22
24
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
23
25
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
24
26
|
const {
|
|
@@ -48,6 +50,20 @@ class VitestPlugin extends CiPlugin {
|
|
|
48
50
|
onDone(!testsForThisTestSuite.includes(testName))
|
|
49
51
|
})
|
|
50
52
|
|
|
53
|
+
this.addSub('ci:vitest:test:is-quarantined', ({ quarantinedTests, testSuiteAbsolutePath, testName, onDone }) => {
|
|
54
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
55
|
+
const isQuarantined = quarantinedTests
|
|
56
|
+
?.vitest
|
|
57
|
+
?.suites
|
|
58
|
+
?.[testSuite]
|
|
59
|
+
?.tests
|
|
60
|
+
?.[testName]
|
|
61
|
+
?.properties
|
|
62
|
+
?.quarantined
|
|
63
|
+
|
|
64
|
+
onDone(isQuarantined ?? false)
|
|
65
|
+
})
|
|
66
|
+
|
|
51
67
|
this.addSub('ci:vitest:is-early-flake-detection-faulty', ({
|
|
52
68
|
knownTests,
|
|
53
69
|
testFilepaths,
|
|
@@ -66,6 +82,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
66
82
|
testSuiteAbsolutePath,
|
|
67
83
|
isRetry,
|
|
68
84
|
isNew,
|
|
85
|
+
isQuarantined,
|
|
69
86
|
mightHitProbe,
|
|
70
87
|
isRetryReasonEfd
|
|
71
88
|
}) => {
|
|
@@ -84,6 +101,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
84
101
|
if (isRetryReasonEfd) {
|
|
85
102
|
extraTags[TEST_RETRY_REASON] = 'efd'
|
|
86
103
|
}
|
|
104
|
+
if (isQuarantined) {
|
|
105
|
+
extraTags[TEST_MANAGEMENT_IS_QUARANTINED] = 'true'
|
|
106
|
+
}
|
|
87
107
|
|
|
88
108
|
const span = this.startTestSpan(
|
|
89
109
|
testName,
|
|
@@ -257,6 +277,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
257
277
|
testCodeCoverageLinesTotal,
|
|
258
278
|
isEarlyFlakeDetectionEnabled,
|
|
259
279
|
isEarlyFlakeDetectionFaulty,
|
|
280
|
+
isQuarantinedTestsEnabled,
|
|
260
281
|
onFinish
|
|
261
282
|
}) => {
|
|
262
283
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -275,6 +296,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
275
296
|
if (isEarlyFlakeDetectionFaulty) {
|
|
276
297
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
277
298
|
}
|
|
299
|
+
if (isQuarantinedTestsEnabled) {
|
|
300
|
+
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
301
|
+
}
|
|
278
302
|
this.testModuleSpan.finish()
|
|
279
303
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
280
304
|
this.testSessionSpan.finish()
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
3
|
const { CODE_INJECTION } = require('../vulnerabilities')
|
|
4
|
+
const StoredInjectionAnalyzer = require('./stored-injection-analyzer')
|
|
5
5
|
const { INSTRUMENTED_SINK } = require('../telemetry/iast-metric')
|
|
6
6
|
const { storage } = require('../../../../../datadog-core')
|
|
7
7
|
const { getIastContext } = require('../iast-context')
|
|
8
8
|
|
|
9
|
-
class CodeInjectionAnalyzer extends
|
|
9
|
+
class CodeInjectionAnalyzer extends StoredInjectionAnalyzer {
|
|
10
10
|
constructor () {
|
|
11
11
|
super(CODE_INJECTION)
|
|
12
12
|
this.evalInstrumentedInc = false
|
|
@@ -31,10 +31,6 @@ class CodeInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
31
31
|
this.addSub('datadog:vm:run-script:start', ({ code }) => this.analyze(code))
|
|
32
32
|
this.addSub('datadog:vm:source-text-module:start', ({ code }) => this.analyze(code))
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
_areRangesVulnerable () {
|
|
36
|
-
return true
|
|
37
|
-
}
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
module.exports = new CodeInjectionAnalyzer()
|
|
@@ -5,8 +5,13 @@ const { SQL_ROW_VALUE } = require('../taint-tracking/source-types')
|
|
|
5
5
|
|
|
6
6
|
class InjectionAnalyzer extends Analyzer {
|
|
7
7
|
_isVulnerable (value, iastContext) {
|
|
8
|
-
|
|
8
|
+
let ranges = value && getRanges(iastContext, value)
|
|
9
9
|
if (ranges?.length > 0) {
|
|
10
|
+
ranges = this._filterSecureRanges(ranges)
|
|
11
|
+
if (!ranges?.length) {
|
|
12
|
+
this._incrementSuppressedMetric(iastContext)
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
return this._areRangesVulnerable(ranges)
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -21,6 +26,15 @@ class InjectionAnalyzer extends Analyzer {
|
|
|
21
26
|
_areRangesVulnerable (ranges) {
|
|
22
27
|
return ranges?.some(range => range.iinfo.type !== SQL_ROW_VALUE)
|
|
23
28
|
}
|
|
29
|
+
|
|
30
|
+
_filterSecureRanges (ranges) {
|
|
31
|
+
return ranges?.filter(range => !this._isRangeSecure(range))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_isRangeSecure (range) {
|
|
35
|
+
const { secureMarks } = range
|
|
36
|
+
return (secureMarks & this._secureMark) === this._secureMark
|
|
37
|
+
}
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
module.exports = InjectionAnalyzer
|
|
@@ -4,29 +4,13 @@ const InjectionAnalyzer = require('./injection-analyzer')
|
|
|
4
4
|
const { NOSQL_MONGODB_INJECTION } = require('../vulnerabilities')
|
|
5
5
|
const { getRanges, addSecureMark } = require('../taint-tracking/operations')
|
|
6
6
|
const { getNodeModulesPaths } = require('../path-line')
|
|
7
|
-
const { getNextSecureMark } = require('../taint-tracking/secure-marks-generator')
|
|
8
7
|
const { storage } = require('../../../../../datadog-core')
|
|
9
8
|
const { getIastContext } = require('../iast-context')
|
|
10
9
|
const { HTTP_REQUEST_PARAMETER, HTTP_REQUEST_BODY } = require('../taint-tracking/source-types')
|
|
11
10
|
|
|
12
11
|
const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose', 'mquery')
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
function iterateObjectStrings (target, fn, levelKeys = [], depth = 20, visited = new Set()) {
|
|
16
|
-
if (target !== null && typeof target === 'object') {
|
|
17
|
-
Object.keys(target).forEach((key) => {
|
|
18
|
-
const nextLevelKeys = [...levelKeys, key]
|
|
19
|
-
const val = target[key]
|
|
20
|
-
|
|
21
|
-
if (typeof val === 'string') {
|
|
22
|
-
fn(val, nextLevelKeys, target, key)
|
|
23
|
-
} else if (depth > 0 && !visited.has(val)) {
|
|
24
|
-
iterateObjectStrings(val, fn, nextLevelKeys, depth - 1, visited)
|
|
25
|
-
visited.add(val)
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
}
|
|
12
|
+
const { NOSQL_MONGODB_INJECTION_MARK } = require('../taint-tracking/secure-marks')
|
|
13
|
+
const { iterateObjectStrings } = require('../utils')
|
|
30
14
|
|
|
31
15
|
class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
32
16
|
constructor () {
|
|
@@ -88,7 +72,7 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
|
88
72
|
const currentLevelKey = levelKeys[i]
|
|
89
73
|
|
|
90
74
|
if (i === levelsLength - 1) {
|
|
91
|
-
parentObj[currentLevelKey] = addSecureMark(iastContext, value,
|
|
75
|
+
parentObj[currentLevelKey] = addSecureMark(iastContext, value, NOSQL_MONGODB_INJECTION_MARK)
|
|
92
76
|
} else {
|
|
93
77
|
parentObj = parentObj[currentLevelKey]
|
|
94
78
|
}
|
|
@@ -106,7 +90,7 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
|
106
90
|
if (iastContext) { // do nothing if we are not in an iast request
|
|
107
91
|
iterateObjectStrings(sanitizedObject, function (value, levelKeys, parent, lastKey) {
|
|
108
92
|
try {
|
|
109
|
-
parent[lastKey] = addSecureMark(iastContext, value,
|
|
93
|
+
parent[lastKey] = addSecureMark(iastContext, value, NOSQL_MONGODB_INJECTION_MARK)
|
|
110
94
|
} catch {
|
|
111
95
|
// if it is a readonly property, do nothing
|
|
112
96
|
}
|
|
@@ -121,8 +105,7 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
|
121
105
|
|
|
122
106
|
_isVulnerableRange (range) {
|
|
123
107
|
const rangeType = range?.iinfo?.type
|
|
124
|
-
|
|
125
|
-
return isVulnerableType && (range.secureMarks & MONGODB_NOSQL_SECURE_MARK) !== MONGODB_NOSQL_SECURE_MARK
|
|
108
|
+
return rangeType === HTTP_REQUEST_PARAMETER || rangeType === HTTP_REQUEST_BODY
|
|
126
109
|
}
|
|
127
110
|
|
|
128
111
|
_isVulnerable (value, iastContext) {
|
|
@@ -137,10 +120,15 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
|
137
120
|
const allRanges = []
|
|
138
121
|
|
|
139
122
|
iterateObjectStrings(value.filter, (val, nextLevelKeys) => {
|
|
140
|
-
|
|
123
|
+
let ranges = getRanges(iastContext, val)
|
|
141
124
|
if (ranges?.length) {
|
|
142
125
|
const filteredRanges = []
|
|
143
126
|
|
|
127
|
+
ranges = this._filterSecureRanges(ranges)
|
|
128
|
+
if (!ranges.length) {
|
|
129
|
+
this._incrementSuppressedMetric(iastContext)
|
|
130
|
+
}
|
|
131
|
+
|
|
144
132
|
for (const range of ranges) {
|
|
145
133
|
if (this._isVulnerableRange(range)) {
|
|
146
134
|
isVulnerable = true
|
|
@@ -175,4 +163,3 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
|
175
163
|
}
|
|
176
164
|
|
|
177
165
|
module.exports = new NosqlInjectionMongodbAnalyzer()
|
|
178
|
-
module.exports.MONGODB_NOSQL_SECURE_MARK = MONGODB_NOSQL_SECURE_MARK
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
3
|
const { SQL_INJECTION } = require('../vulnerabilities')
|
|
5
4
|
const { getRanges } = require('../taint-tracking/operations')
|
|
6
5
|
const { storage } = require('../../../../../datadog-core')
|
|
7
6
|
const { getNodeModulesPaths } = require('../path-line')
|
|
7
|
+
const StoredInjectionAnalyzer = require('./stored-injection-analyzer')
|
|
8
8
|
|
|
9
9
|
const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool', 'knex')
|
|
10
10
|
|
|
11
|
-
class SqlInjectionAnalyzer extends
|
|
11
|
+
class SqlInjectionAnalyzer extends StoredInjectionAnalyzer {
|
|
12
12
|
constructor () {
|
|
13
13
|
super(SQL_INJECTION)
|
|
14
14
|
}
|
|
@@ -82,10 +82,6 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
82
82
|
return knexDialect.toUpperCase()
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
_areRangesVulnerable () {
|
|
87
|
-
return true
|
|
88
|
-
}
|
|
89
85
|
}
|
|
90
86
|
|
|
91
87
|
module.exports = new SqlInjectionAnalyzer()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
3
|
const { TEMPLATE_INJECTION } = require('../vulnerabilities')
|
|
4
|
+
const StoredInjectionAnalyzer = require('./stored-injection-analyzer')
|
|
5
5
|
|
|
6
|
-
class TemplateInjectionAnalyzer extends
|
|
6
|
+
class TemplateInjectionAnalyzer extends StoredInjectionAnalyzer {
|
|
7
7
|
constructor () {
|
|
8
8
|
super(TEMPLATE_INJECTION)
|
|
9
9
|
}
|
|
@@ -13,10 +13,6 @@ class TemplateInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
13
13
|
this.addSub('datadog:handlebars:register-partial:start', ({ partial }) => this.analyze(partial))
|
|
14
14
|
this.addSub('datadog:pug:compile:start', ({ source }) => this.analyze(source))
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
_areRangesVulnerable () {
|
|
18
|
-
return true
|
|
19
|
-
}
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
module.exports = new TemplateInjectionAnalyzer()
|
|
@@ -10,11 +10,14 @@ const {
|
|
|
10
10
|
getVulnerabilityCallSiteFrames,
|
|
11
11
|
replaceCallSiteFromSourceMap
|
|
12
12
|
} = require('../vulnerability-reporter')
|
|
13
|
+
const { getMarkFromVulnerabilityType } = require('../taint-tracking/secure-marks')
|
|
14
|
+
const { SUPPRESSED_VULNERABILITIES } = require('../telemetry/iast-metric')
|
|
13
15
|
|
|
14
16
|
class Analyzer extends SinkIastPlugin {
|
|
15
17
|
constructor (type) {
|
|
16
18
|
super()
|
|
17
19
|
this._type = type
|
|
20
|
+
this._secureMark = getMarkFromVulnerabilityType(type)
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
_isVulnerable (value, context) {
|
|
@@ -75,11 +78,17 @@ class Analyzer extends SinkIastPlugin {
|
|
|
75
78
|
if (locationFromSourceMap?.path) {
|
|
76
79
|
originalLocation.path = locationFromSourceMap.path
|
|
77
80
|
}
|
|
81
|
+
|
|
78
82
|
if (locationFromSourceMap?.line) {
|
|
79
83
|
originalLocation.line = locationFromSourceMap.line
|
|
80
84
|
}
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
|
|
86
|
+
if (location?.class_name) {
|
|
87
|
+
originalLocation.class = location.class_name
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (location?.function) {
|
|
91
|
+
originalLocation.method = location.function
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
return originalLocation
|
|
@@ -149,6 +158,17 @@ class Analyzer extends SinkIastPlugin {
|
|
|
149
158
|
return hash
|
|
150
159
|
}
|
|
151
160
|
|
|
161
|
+
_getSuppressedMetricTag () {
|
|
162
|
+
if (!this._suppressedMetricTag) {
|
|
163
|
+
this._suppressedMetricTag = SUPPRESSED_VULNERABILITIES.formatTags(this._type)[0]
|
|
164
|
+
}
|
|
165
|
+
return this._suppressedMetricTag
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_incrementSuppressedMetric (iastContext) {
|
|
169
|
+
SUPPRESSED_VULNERABILITIES.inc(iastContext, this._getSuppressedMetricTag())
|
|
170
|
+
}
|
|
171
|
+
|
|
152
172
|
addSub (iastSubOrChannelName, handler) {
|
|
153
173
|
const iastSub = typeof iastSubOrChannelName === 'string'
|
|
154
174
|
? { channelName: iastSubOrChannelName }
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
const { IAST_ENABLED_TAG_KEY } = require('./tags')
|
|
16
16
|
const iastTelemetry = require('./telemetry')
|
|
17
17
|
const { enable: enableFsPlugin, disable: disableFsPlugin, IAST_MODULE } = require('../rasp/fs-plugin')
|
|
18
|
+
const securityControls = require('./security-controls')
|
|
18
19
|
|
|
19
20
|
// TODO Change to `apm:http:server:request:[start|close]` when the subscription
|
|
20
21
|
// order of the callbacks can be enforce
|
|
@@ -35,6 +36,7 @@ function enable (config, _tracer) {
|
|
|
35
36
|
requestClose.subscribe(onIncomingHttpRequestEnd)
|
|
36
37
|
overheadController.configure(config.iast)
|
|
37
38
|
overheadController.startGlobalContext()
|
|
39
|
+
securityControls.configure(config.iast)
|
|
38
40
|
vulnerabilityReporter.start(config, _tracer)
|
|
39
41
|
|
|
40
42
|
isEnabled = true
|