dd-trace 4.42.0 → 4.43.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 +0 -2
- package/index.d.ts +61 -39
- package/package.json +7 -11
- package/packages/datadog-instrumentations/src/child_process.js +2 -2
- package/packages/datadog-instrumentations/src/fs.js +1 -1
- package/packages/datadog-instrumentations/src/hapi.js +1 -1
- package/packages/datadog-instrumentations/src/http/client.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +17 -2
- package/packages/datadog-instrumentations/src/kafkajs.js +1 -1
- package/packages/datadog-instrumentations/src/ldapjs.js +2 -2
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/next.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +46 -32
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-instrumentations/src/vitest.js +51 -5
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +79 -42
- package/packages/datadog-plugin-cypress/src/plugin.js +4 -3
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +7 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +1 -1
- package/packages/datadog-plugin-mongodb-core/src/index.js +1 -1
- package/packages/datadog-plugin-openai/src/index.js +5 -5
- package/packages/datadog-plugin-playwright/src/index.js +4 -1
- package/packages/datadog-plugin-sharedb/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +17 -6
- package/packages/dd-trace/src/analytics_sampler.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +3 -1
- package/packages/dd-trace/src/appsec/index.js +3 -3
- package/packages/dd-trace/src/appsec/passport.js +1 -1
- package/packages/dd-trace/src/appsec/reporter.js +0 -4
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
- package/packages/dd-trace/src/config.js +27 -24
- package/packages/dd-trace/src/datastreams/processor.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +6 -0
|
@@ -21,7 +21,7 @@ class DynamoDb extends BaseAwsSdkPlugin {
|
|
|
21
21
|
// batch operations have different format, collect table name for batch
|
|
22
22
|
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#batchGetItem-property`
|
|
23
23
|
// dynamoDB batch TableName
|
|
24
|
-
if (params.RequestItems) {
|
|
24
|
+
if (params.RequestItems !== null) {
|
|
25
25
|
if (typeof params.RequestItems === 'object') {
|
|
26
26
|
if (Object.keys(params.RequestItems).length === 1) {
|
|
27
27
|
const tableName = Object.keys(params.RequestItems)[0]
|
|
@@ -47,7 +47,7 @@ class Stepfunctions extends BaseAwsSdkPlugin {
|
|
|
47
47
|
|
|
48
48
|
try {
|
|
49
49
|
const inputObj = JSON.parse(input)
|
|
50
|
-
if (inputObj && typeof inputObj === 'object') {
|
|
50
|
+
if (inputObj !== null && typeof inputObj === 'object') {
|
|
51
51
|
// We've parsed the input JSON string
|
|
52
52
|
inputObj._datadog = {}
|
|
53
53
|
this.tracer.inject(span, 'text_map', inputObj._datadog)
|
|
@@ -7,10 +7,10 @@ const PROCESS_DENYLIST = ['md5']
|
|
|
7
7
|
|
|
8
8
|
const VARNAMES_REGEX = /\$([\w\d_]*)(?:[^\w\d_]|$)/gmi
|
|
9
9
|
// eslint-disable-next-line max-len
|
|
10
|
-
const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|
|
|
10
|
+
const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|address|api[-_]?key|e?mail|secret(?:[-_]?key)?|a(?:ccess|uth)[-_]?token|mysql_pwd|credentials|(?:stripe)?token)$'
|
|
11
11
|
const regexParam = new RegExp(PARAM_PATTERN, 'i')
|
|
12
12
|
const ENV_PATTERN = '^(\\w+=\\w+;)*\\w+=\\w+;?$'
|
|
13
|
-
const
|
|
13
|
+
const envVarRegex = new RegExp(ENV_PATTERN)
|
|
14
14
|
const REDACTED = '?'
|
|
15
15
|
|
|
16
16
|
function extractVarNames (expression) {
|
|
@@ -61,7 +61,9 @@ function scrubChildProcessCmd (expression) {
|
|
|
61
61
|
for (let index = 0; index < expressionTokens.length; index++) {
|
|
62
62
|
const token = expressionTokens[index]
|
|
63
63
|
|
|
64
|
-
if (
|
|
64
|
+
if (token === null) {
|
|
65
|
+
continue
|
|
66
|
+
} else if (typeof token === 'object') {
|
|
65
67
|
if (token.pattern) {
|
|
66
68
|
result.push(token.pattern)
|
|
67
69
|
} else if (token.op) {
|
|
@@ -70,7 +72,7 @@ function scrubChildProcessCmd (expression) {
|
|
|
70
72
|
result.push(`#${token.comment}`)
|
|
71
73
|
}
|
|
72
74
|
} else if (!foundBinary) {
|
|
73
|
-
if (
|
|
75
|
+
if (envVarRegex.test(token)) {
|
|
74
76
|
const envSplit = token.split('=')
|
|
75
77
|
|
|
76
78
|
if (!ALLOWED_ENV_VARIABLES.includes(envSplit[0])) {
|
|
@@ -28,7 +28,8 @@ const {
|
|
|
28
28
|
TEST_SOURCE_FILE,
|
|
29
29
|
TEST_IS_NEW,
|
|
30
30
|
TEST_IS_RETRY,
|
|
31
|
-
TEST_EARLY_FLAKE_ENABLED
|
|
31
|
+
TEST_EARLY_FLAKE_ENABLED,
|
|
32
|
+
NUM_FAILED_TEST_RETRIES
|
|
32
33
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
33
34
|
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
34
35
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -207,10 +208,40 @@ class CypressPlugin {
|
|
|
207
208
|
this.knownTests = []
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
// Init function returns a promise that resolves with the Cypress configuration
|
|
212
|
+
// Depending on the received configuration, the Cypress configuration can be modified:
|
|
213
|
+
// for example, to enable retries for failed tests.
|
|
210
214
|
init (tracer, cypressConfig) {
|
|
211
215
|
this._isInit = true
|
|
212
216
|
this.tracer = tracer
|
|
213
217
|
this.cypressConfig = cypressConfig
|
|
218
|
+
|
|
219
|
+
this.libraryConfigurationPromise = getLibraryConfiguration(this.tracer, this.testConfiguration)
|
|
220
|
+
.then((libraryConfigurationResponse) => {
|
|
221
|
+
if (libraryConfigurationResponse.err) {
|
|
222
|
+
log.error(libraryConfigurationResponse.err)
|
|
223
|
+
} else {
|
|
224
|
+
const {
|
|
225
|
+
libraryConfig: {
|
|
226
|
+
isSuitesSkippingEnabled,
|
|
227
|
+
isCodeCoverageEnabled,
|
|
228
|
+
isEarlyFlakeDetectionEnabled,
|
|
229
|
+
earlyFlakeDetectionNumRetries,
|
|
230
|
+
isFlakyTestRetriesEnabled
|
|
231
|
+
}
|
|
232
|
+
} = libraryConfigurationResponse
|
|
233
|
+
this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
|
|
234
|
+
this.isCodeCoverageEnabled = isCodeCoverageEnabled
|
|
235
|
+
this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
|
|
236
|
+
this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
|
|
237
|
+
this.isFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
|
|
238
|
+
if (this.isFlakyTestRetriesEnabled) {
|
|
239
|
+
this.cypressConfig.retries.runMode = NUM_FAILED_TEST_RETRIES
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return this.cypressConfig
|
|
243
|
+
})
|
|
244
|
+
return this.libraryConfigurationPromise
|
|
214
245
|
}
|
|
215
246
|
|
|
216
247
|
getTestSuiteSpan (suite) {
|
|
@@ -297,29 +328,13 @@ class CypressPlugin {
|
|
|
297
328
|
}
|
|
298
329
|
|
|
299
330
|
async beforeRun (details) {
|
|
331
|
+
// We need to make sure that the plugin is initialized before running the tests
|
|
332
|
+
// This is for the case where the user has not returned the promise from the init function
|
|
333
|
+
await this.libraryConfigurationPromise
|
|
300
334
|
this.command = getCypressCommand(details)
|
|
301
335
|
this.frameworkVersion = getCypressVersion(details)
|
|
302
336
|
this.rootDir = getRootDir(details)
|
|
303
337
|
|
|
304
|
-
const libraryConfigurationResponse = await getLibraryConfiguration(this.tracer, this.testConfiguration)
|
|
305
|
-
|
|
306
|
-
if (libraryConfigurationResponse.err) {
|
|
307
|
-
log.error(libraryConfigurationResponse.err)
|
|
308
|
-
} else {
|
|
309
|
-
const {
|
|
310
|
-
libraryConfig: {
|
|
311
|
-
isSuitesSkippingEnabled,
|
|
312
|
-
isCodeCoverageEnabled,
|
|
313
|
-
isEarlyFlakeDetectionEnabled,
|
|
314
|
-
earlyFlakeDetectionNumRetries
|
|
315
|
-
}
|
|
316
|
-
} = libraryConfigurationResponse
|
|
317
|
-
this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
|
|
318
|
-
this.isCodeCoverageEnabled = isCodeCoverageEnabled
|
|
319
|
-
this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
|
|
320
|
-
this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
|
|
321
|
-
}
|
|
322
|
-
|
|
323
338
|
if (this.isEarlyFlakeDetectionEnabled) {
|
|
324
339
|
const knownTestsResponse = await getKnownTests(
|
|
325
340
|
this.tracer,
|
|
@@ -485,29 +500,51 @@ class CypressPlugin {
|
|
|
485
500
|
// This is not always the case, such as when an `after` hook fails:
|
|
486
501
|
// Cypress will report the last run test as failed, but we don't know that yet at `dd:afterEach`
|
|
487
502
|
let latestError
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (!
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
if (cypressTest.displayError) {
|
|
494
|
-
latestError = new Error(cypressTest.displayError)
|
|
495
|
-
}
|
|
496
|
-
const cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
|
|
497
|
-
// update test status
|
|
498
|
-
if (cypressTestStatus !== finishedTest.testStatus) {
|
|
499
|
-
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
|
|
500
|
-
finishedTest.testSpan.setTag('error', latestError)
|
|
501
|
-
}
|
|
502
|
-
if (this.itrCorrelationId) {
|
|
503
|
-
finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
|
|
504
|
-
}
|
|
505
|
-
if (spec.absolute && this.repositoryRoot) {
|
|
506
|
-
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, this.repositoryRoot))
|
|
507
|
-
} else {
|
|
508
|
-
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative)
|
|
503
|
+
|
|
504
|
+
const finishedTestsByTestName = finishedTests.reduce((acc, finishedTest) => {
|
|
505
|
+
if (!acc[finishedTest.testName]) {
|
|
506
|
+
acc[finishedTest.testName] = []
|
|
509
507
|
}
|
|
510
|
-
finishedTest.
|
|
508
|
+
acc[finishedTest.testName].push(finishedTest)
|
|
509
|
+
return acc
|
|
510
|
+
}, {})
|
|
511
|
+
|
|
512
|
+
Object.entries(finishedTestsByTestName).forEach(([testName, finishedTestAttempts]) => {
|
|
513
|
+
finishedTestAttempts.forEach((finishedTest, attemptIndex) => {
|
|
514
|
+
// TODO: there could be multiple if there have been retries!
|
|
515
|
+
// potentially we need to match the test status!
|
|
516
|
+
const cypressTest = cypressTests.find(test => test.title.join(' ') === testName)
|
|
517
|
+
if (!cypressTest) {
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
// finishedTests can include multiple tests with the same name if they have been retried
|
|
521
|
+
// by early flake detection. Cypress is unaware of this so .attempts does not necessarily have
|
|
522
|
+
// the same length as `finishedTestAttempts`
|
|
523
|
+
let cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
|
|
524
|
+
if (cypressTest.attempts && cypressTest.attempts[attemptIndex]) {
|
|
525
|
+
cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.attempts[attemptIndex].state]
|
|
526
|
+
if (attemptIndex > 0) {
|
|
527
|
+
finishedTest.testSpan.setTag(TEST_IS_RETRY, 'true')
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (cypressTest.displayError) {
|
|
531
|
+
latestError = new Error(cypressTest.displayError)
|
|
532
|
+
}
|
|
533
|
+
// Update test status
|
|
534
|
+
if (cypressTestStatus !== finishedTest.testStatus) {
|
|
535
|
+
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
|
|
536
|
+
finishedTest.testSpan.setTag('error', latestError)
|
|
537
|
+
}
|
|
538
|
+
if (this.itrCorrelationId) {
|
|
539
|
+
finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
|
|
540
|
+
}
|
|
541
|
+
if (spec.absolute && this.repositoryRoot) {
|
|
542
|
+
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, this.repositoryRoot))
|
|
543
|
+
} else {
|
|
544
|
+
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative)
|
|
545
|
+
}
|
|
546
|
+
finishedTest.testSpan.finish(finishedTest.finishTime)
|
|
547
|
+
})
|
|
511
548
|
})
|
|
512
549
|
|
|
513
550
|
if (this.testSuiteSpan) {
|
|
@@ -22,13 +22,14 @@ module.exports = (on, config) => {
|
|
|
22
22
|
// The tracer was not init correctly for whatever reason (such as invalid DD_SITE)
|
|
23
23
|
if (tracer._tracer instanceof NoopTracer) {
|
|
24
24
|
// We still need to register these tasks or the support file will fail
|
|
25
|
-
|
|
25
|
+
on('task', noopTask)
|
|
26
|
+
return config
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
cypressPlugin.init(tracer, config)
|
|
29
|
-
|
|
30
29
|
on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
|
|
31
30
|
on('after:spec', cypressPlugin.afterSpec.bind(cypressPlugin))
|
|
32
31
|
on('after:run', cypressPlugin.afterRun.bind(cypressPlugin))
|
|
33
32
|
on('task', cypressPlugin.getTasks())
|
|
33
|
+
|
|
34
|
+
return cypressPlugin.init(tracer, config)
|
|
34
35
|
}
|
|
@@ -29,7 +29,7 @@ class FsPlugin extends TracingPlugin {
|
|
|
29
29
|
resource: operation,
|
|
30
30
|
kind: 'internal',
|
|
31
31
|
meta: {
|
|
32
|
-
'file.descriptor': (typeof fd === 'object' || typeof fd === 'number') ? fd.toString() : '',
|
|
32
|
+
'file.descriptor': ((fd !== null && typeof fd === 'object') || typeof fd === 'number') ? fd.toString() : '',
|
|
33
33
|
'file.dest': params.dest || params.newPath || (params.target && params.path),
|
|
34
34
|
'file.flag': String(flag || defaultFlag || ''),
|
|
35
35
|
'file.gid': gid || '',
|
|
@@ -148,6 +148,7 @@ class JestPlugin extends CiPlugin {
|
|
|
148
148
|
config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
|
|
149
149
|
config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
|
|
150
150
|
config._ddRepositoryRoot = this.repositoryRoot
|
|
151
|
+
config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
|
|
151
152
|
})
|
|
152
153
|
})
|
|
153
154
|
|
|
@@ -324,7 +325,8 @@ class JestPlugin extends CiPlugin {
|
|
|
324
325
|
testStartLine,
|
|
325
326
|
testSourceFile,
|
|
326
327
|
isNew,
|
|
327
|
-
isEfdRetry
|
|
328
|
+
isEfdRetry,
|
|
329
|
+
isJestRetry
|
|
328
330
|
} = test
|
|
329
331
|
|
|
330
332
|
const extraTags = {
|
|
@@ -349,6 +351,10 @@ class JestPlugin extends CiPlugin {
|
|
|
349
351
|
}
|
|
350
352
|
}
|
|
351
353
|
|
|
354
|
+
if (isJestRetry) {
|
|
355
|
+
extraTags[TEST_IS_RETRY] = 'true'
|
|
356
|
+
}
|
|
357
|
+
|
|
352
358
|
return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
|
|
353
359
|
}
|
|
354
360
|
}
|
|
@@ -81,7 +81,7 @@ class KafkajsProducerPlugin extends ProducerPlugin {
|
|
|
81
81
|
span.setTag(BOOTSTRAP_SERVERS_KEY, bootstrapServers)
|
|
82
82
|
}
|
|
83
83
|
for (const message of messages) {
|
|
84
|
-
if (typeof message === 'object') {
|
|
84
|
+
if (message !== null && typeof message === 'object') {
|
|
85
85
|
this.tracer.inject(span, 'text_map', message.headers)
|
|
86
86
|
if (this.config.dsmEnabled) {
|
|
87
87
|
const payloadSize = getMessageSize(message)
|
|
@@ -115,7 +115,7 @@ function limitDepth (input) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
function isObject (val) {
|
|
118
|
-
return typeof val === 'object' &&
|
|
118
|
+
return val !== null && typeof val === 'object' && !Array.isArray(val)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function isBSON (val) {
|
|
@@ -118,7 +118,7 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// createChatCompletion, createCompletion
|
|
121
|
-
if (typeof payload.logit_bias === 'object'
|
|
121
|
+
if (payload.logit_bias !== null && typeof payload.logit_bias === 'object') {
|
|
122
122
|
for (const [tokenId, bias] of Object.entries(payload.logit_bias)) {
|
|
123
123
|
tags[`openai.request.logit_bias.${tokenId}`] = bias
|
|
124
124
|
}
|
|
@@ -427,14 +427,14 @@ function createChatCompletionRequestExtraction (tags, payload, store) {
|
|
|
427
427
|
function commonCreateImageRequestExtraction (tags, payload, store) {
|
|
428
428
|
// createImageEdit, createImageVariation
|
|
429
429
|
const img = payload.file || payload.image
|
|
430
|
-
if (img && typeof img === 'object' && img.path) {
|
|
430
|
+
if (img !== null && typeof img === 'object' && img.path) {
|
|
431
431
|
const file = path.basename(img.path)
|
|
432
432
|
tags['openai.request.image'] = file
|
|
433
433
|
store.file = file
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
// createImageEdit
|
|
437
|
-
if (payload.mask && typeof payload.mask === 'object' && payload.mask.path) {
|
|
437
|
+
if (payload.mask !== null && typeof payload.mask === 'object' && payload.mask.path) {
|
|
438
438
|
const mask = path.basename(payload.mask.path)
|
|
439
439
|
tags['openai.request.mask'] = mask
|
|
440
440
|
store.mask = mask
|
|
@@ -633,7 +633,7 @@ function commonCreateAudioRequestExtraction (tags, body, store) {
|
|
|
633
633
|
tags['openai.request.response_format'] = body.response_format
|
|
634
634
|
tags['openai.request.language'] = body.language
|
|
635
635
|
|
|
636
|
-
if (body.file && typeof body.file === 'object' && body.file.path) {
|
|
636
|
+
if (body.file !== null && typeof body.file === 'object' && body.file.path) {
|
|
637
637
|
const filename = path.basename(body.file.path)
|
|
638
638
|
tags['openai.request.filename'] = filename
|
|
639
639
|
store.file = filename
|
|
@@ -646,7 +646,7 @@ function commonFileRequestExtraction (tags, body) {
|
|
|
646
646
|
// User can provider either exact file contents or a file read stream
|
|
647
647
|
// With the stream we extract the filepath
|
|
648
648
|
// This is a best effort attempt to extract the filename during the request
|
|
649
|
-
if (body.file && typeof body.file === 'object' && body.file.path) {
|
|
649
|
+
if (body.file !== null && typeof body.file === 'object' && body.file.path) {
|
|
650
650
|
tags['openai.request.filename'] = path.basename(body.file.path)
|
|
651
651
|
}
|
|
652
652
|
}
|
|
@@ -116,7 +116,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
116
116
|
|
|
117
117
|
this.enter(span, store)
|
|
118
118
|
})
|
|
119
|
-
this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry }) => {
|
|
119
|
+
this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry, isRetry }) => {
|
|
120
120
|
const store = storage.getStore()
|
|
121
121
|
const span = store && store.span
|
|
122
122
|
if (!span) return
|
|
@@ -135,6 +135,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
135
135
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
+
if (isRetry) {
|
|
139
|
+
span.setTag(TEST_IS_RETRY, 'true')
|
|
140
|
+
}
|
|
138
141
|
|
|
139
142
|
steps.forEach(step => {
|
|
140
143
|
const stepStartTime = step.startTime.getTime()
|
|
@@ -6,7 +6,8 @@ const {
|
|
|
6
6
|
finishAllTraceSpans,
|
|
7
7
|
getTestSuitePath,
|
|
8
8
|
getTestSuiteCommonTags,
|
|
9
|
-
TEST_SOURCE_FILE
|
|
9
|
+
TEST_SOURCE_FILE,
|
|
10
|
+
TEST_IS_RETRY
|
|
10
11
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
11
12
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
12
13
|
|
|
@@ -25,16 +26,22 @@ class VitestPlugin extends CiPlugin {
|
|
|
25
26
|
|
|
26
27
|
this.taskToFinishTime = new WeakMap()
|
|
27
28
|
|
|
28
|
-
this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath }) => {
|
|
29
|
+
this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath, isRetry }) => {
|
|
29
30
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
30
31
|
const store = storage.getStore()
|
|
32
|
+
|
|
33
|
+
const extraTags = {
|
|
34
|
+
[TEST_SOURCE_FILE]: testSuite
|
|
35
|
+
}
|
|
36
|
+
if (isRetry) {
|
|
37
|
+
extraTags[TEST_IS_RETRY] = 'true'
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
const span = this.startTestSpan(
|
|
32
41
|
testName,
|
|
33
42
|
testSuite,
|
|
34
43
|
this.testSuiteSpan,
|
|
35
|
-
|
|
36
|
-
[TEST_SOURCE_FILE]: testSuite
|
|
37
|
-
}
|
|
44
|
+
extraTags
|
|
38
45
|
)
|
|
39
46
|
|
|
40
47
|
this.enter(span, store)
|
|
@@ -73,7 +80,11 @@ class VitestPlugin extends CiPlugin {
|
|
|
73
80
|
if (error) {
|
|
74
81
|
span.setTag('error', error)
|
|
75
82
|
}
|
|
76
|
-
|
|
83
|
+
if (duration) {
|
|
84
|
+
span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
|
|
85
|
+
} else {
|
|
86
|
+
span.finish() // retries will not have a duration
|
|
87
|
+
}
|
|
77
88
|
finishAllTraceSpans(span)
|
|
78
89
|
}
|
|
79
90
|
})
|
|
@@ -4,7 +4,7 @@ const { MEASURED } = require('../../../ext/tags')
|
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
sample (span, measured, measuredByDefault) {
|
|
7
|
-
if (typeof measured === 'object') {
|
|
7
|
+
if (measured !== null && typeof measured === 'object') {
|
|
8
8
|
this.sample(span, measured[span.context()._name], measuredByDefault)
|
|
9
9
|
} else if (measured !== undefined) {
|
|
10
10
|
span.setTag(MEASURED, !!measured)
|
|
@@ -13,7 +13,7 @@ const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose', 'mq
|
|
|
13
13
|
const MONGODB_NOSQL_SECURE_MARK = getNextSecureMark()
|
|
14
14
|
|
|
15
15
|
function iterateObjectStrings (target, fn, levelKeys = [], depth = 20, visited = new Set()) {
|
|
16
|
-
if (target && typeof target === 'object') {
|
|
16
|
+
if (target !== null && typeof target === 'object') {
|
|
17
17
|
Object.keys(target).forEach((key) => {
|
|
18
18
|
const nextLevelKeys = [...levelKeys, key]
|
|
19
19
|
const val = target[key]
|
|
@@ -45,7 +45,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
45
45
|
this.addSub(
|
|
46
46
|
{ channelName: 'apm:express:middleware:next', tag: HTTP_REQUEST_BODY },
|
|
47
47
|
({ req }) => {
|
|
48
|
-
if (req && req.body && typeof req.body === 'object') {
|
|
48
|
+
if (req && req.body !== null && typeof req.body === 'object') {
|
|
49
49
|
const iastContext = getIastContext(storage.getStore())
|
|
50
50
|
if (iastContext && iastContext.body !== req.body) {
|
|
51
51
|
this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
|
|
@@ -63,7 +63,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
63
63
|
this.addSub(
|
|
64
64
|
{ channelName: 'datadog:express:process_params:start', tag: HTTP_REQUEST_PATH_PARAM },
|
|
65
65
|
({ req }) => {
|
|
66
|
-
if (req && req.params && typeof req.params === 'object') {
|
|
66
|
+
if (req && req.params !== null && typeof req.params === 'object') {
|
|
67
67
|
this._taintTrackingHandler(HTTP_REQUEST_PATH_PARAM, req, 'params')
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -27,14 +27,14 @@ class KafkaConsumerIastPlugin extends SourceIastPlugin {
|
|
|
27
27
|
if (iastContext && message) {
|
|
28
28
|
const { key, value } = message
|
|
29
29
|
|
|
30
|
-
if (key && typeof key === 'object') {
|
|
30
|
+
if (key !== null && typeof key === 'object') {
|
|
31
31
|
shimmer.wrap(key, 'toString',
|
|
32
32
|
toString => this.getToStringWrap(toString, iastContext, KAFKA_MESSAGE_KEY))
|
|
33
33
|
|
|
34
34
|
newTaintedObject(iastContext, key, undefined, KAFKA_MESSAGE_KEY)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
if (value && typeof value === 'object') {
|
|
37
|
+
if (value !== null && typeof value === 'object') {
|
|
38
38
|
shimmer.wrap(value, 'toString',
|
|
39
39
|
toString => this.getToStringWrap(toString, iastContext, KAFKA_MESSAGE_VALUE))
|
|
40
40
|
|
|
@@ -43,6 +43,8 @@ class VulnerabilityFormatter {
|
|
|
43
43
|
const valueParts = []
|
|
44
44
|
let fromIndex = 0
|
|
45
45
|
|
|
46
|
+
if (evidence.value == null) return { valueParts }
|
|
47
|
+
|
|
46
48
|
if (typeof evidence.value === 'object' && evidence.rangesToApply) {
|
|
47
49
|
const { value, ranges } = stringifyWithRanges(evidence.value, evidence.rangesToApply)
|
|
48
50
|
evidence.value = value
|
|
@@ -69,7 +71,7 @@ class VulnerabilityFormatter {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
formatEvidence (type, evidence, sourcesIndexes, sources) {
|
|
72
|
-
if (
|
|
74
|
+
if (evidence.value === undefined) {
|
|
73
75
|
return undefined
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -121,16 +121,16 @@ function incomingHttpEndTranslator ({ req, res }) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// TODO: temporary express instrumentation, will use express plugin later
|
|
124
|
-
if (req.params && typeof req.params === 'object') {
|
|
124
|
+
if (req.params !== null && typeof req.params === 'object') {
|
|
125
125
|
persistent[addresses.HTTP_INCOMING_PARAMS] = req.params
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// we need to keep this to support other cookie parsers
|
|
129
|
-
if (req.cookies && typeof req.cookies === 'object') {
|
|
129
|
+
if (req.cookies !== null && typeof req.cookies === 'object') {
|
|
130
130
|
persistent[addresses.HTTP_INCOMING_COOKIES] = req.cookies
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
if (req.query && typeof req.query === 'object') {
|
|
133
|
+
if (req.query !== null && typeof req.query === 'object') {
|
|
134
134
|
persistent[addresses.HTTP_INCOMING_QUERY] = req.query
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -13,7 +13,7 @@ const regexSdkEvent = new RegExp(SDK_USER_EVENT_PATTERN, 'i')
|
|
|
13
13
|
function isSdkCalled (tags) {
|
|
14
14
|
let called = false
|
|
15
15
|
|
|
16
|
-
if (tags && typeof tags === 'object') {
|
|
16
|
+
if (tags !== null && typeof tags === 'object') {
|
|
17
17
|
called = Object.entries(tags).some(([key, value]) => regexSdkEvent.test(key) && value === 'true')
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -207,10 +207,6 @@ function finishRequest (req, res) {
|
|
|
207
207
|
|
|
208
208
|
// collect some headers even when no attack is detected
|
|
209
209
|
const mandatoryTags = filterHeaders(req.headers, REQUEST_HEADERS_MAP)
|
|
210
|
-
const ua = mandatoryTags['http.request.headers.user-agent']
|
|
211
|
-
if (ua) {
|
|
212
|
-
mandatoryTags['http.useragent'] = ua
|
|
213
|
-
}
|
|
214
210
|
rootSpan.addTags(mandatoryTags)
|
|
215
211
|
|
|
216
212
|
const tags = rootSpan.context()._tags
|
|
@@ -25,7 +25,7 @@ class WAFContextWrapper {
|
|
|
25
25
|
const inputs = {}
|
|
26
26
|
const newAddressesToSkip = new Set(this.addressesToSkip)
|
|
27
27
|
|
|
28
|
-
if (persistent && typeof persistent === 'object') {
|
|
28
|
+
if (persistent !== null && typeof persistent === 'object') {
|
|
29
29
|
// TODO: possible optimization: only send params that haven't already been sent with same value to this wafContext
|
|
30
30
|
for (const key of Object.keys(persistent)) {
|
|
31
31
|
// TODO: requiredAddresses is no longer used due to processor addresses are not included in the list. Check on
|