dd-trace 5.18.0 → 5.20.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/ext/formats.d.ts +1 -0
- package/ext/formats.js +2 -1
- package/index.d.ts +61 -39
- package/init.js +3 -15
- package/package.json +9 -12
- 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/helpers/register.js +13 -11
- package/packages/datadog-instrumentations/src/http/client.js +8 -2
- package/packages/datadog-instrumentations/src/http/server.js +50 -13
- 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/mocha/main.js +21 -8
- 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/process.js +29 -0
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-instrumentations/src/vitest.js +98 -28
- package/packages/datadog-plugin-aws-sdk/src/base.js +16 -2
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +3 -3
- 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 +114 -48
- 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 +19 -7
- package/packages/dd-trace/src/analytics_sampler.js +1 -1
- package/packages/dd-trace/src/appsec/blocking.js +10 -1
- package/packages/dd-trace/src/appsec/channels.js +4 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +2 -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/taint-tracking/taint-tracking-impl.js +11 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/code-injection-sensitive-analyzer.js +25 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +3 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +15 -10
- package/packages/dd-trace/src/appsec/passport.js +1 -1
- package/packages/dd-trace/src/appsec/rasp.js +121 -7
- package/packages/dd-trace/src/appsec/recommended.json +220 -2
- 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 +68 -66
- package/packages/dd-trace/src/data_streams.js +44 -0
- package/packages/dd-trace/src/datastreams/pathway.js +4 -2
- package/packages/dd-trace/src/datastreams/processor.js +1 -1
- package/packages/dd-trace/src/log/index.js +32 -0
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +6 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +43 -0
- package/packages/dd-trace/src/opentracing/tracer.js +10 -6
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -2
- package/packages/dd-trace/src/plugins/plugin.js +12 -1
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/tracer.js +2 -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) {
|
|
@@ -227,7 +258,7 @@ class CypressPlugin {
|
|
|
227
258
|
})
|
|
228
259
|
}
|
|
229
260
|
|
|
230
|
-
getTestSpan (testName, testSuite, isUnskippable, isForcedToRun) {
|
|
261
|
+
getTestSpan ({ testName, testSuite, isUnskippable, isForcedToRun, testSourceFile }) {
|
|
231
262
|
const testSuiteTags = {
|
|
232
263
|
[TEST_COMMAND]: this.command,
|
|
233
264
|
[TEST_COMMAND]: this.command,
|
|
@@ -251,8 +282,11 @@ class CypressPlugin {
|
|
|
251
282
|
...testSpanMetadata
|
|
252
283
|
} = getTestCommonTags(testName, testSuite, this.cypressConfig.version, TEST_FRAMEWORK_NAME)
|
|
253
284
|
|
|
254
|
-
|
|
285
|
+
if (testSourceFile) {
|
|
286
|
+
testSpanMetadata[TEST_SOURCE_FILE] = testSourceFile
|
|
287
|
+
}
|
|
255
288
|
|
|
289
|
+
const codeOwners = this.getTestCodeOwners({ testSuite, testSourceFile })
|
|
256
290
|
if (codeOwners) {
|
|
257
291
|
testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
258
292
|
}
|
|
@@ -297,29 +331,13 @@ class CypressPlugin {
|
|
|
297
331
|
}
|
|
298
332
|
|
|
299
333
|
async beforeRun (details) {
|
|
334
|
+
// We need to make sure that the plugin is initialized before running the tests
|
|
335
|
+
// This is for the case where the user has not returned the promise from the init function
|
|
336
|
+
await this.libraryConfigurationPromise
|
|
300
337
|
this.command = getCypressCommand(details)
|
|
301
338
|
this.frameworkVersion = getCypressVersion(details)
|
|
302
339
|
this.rootDir = getRootDir(details)
|
|
303
340
|
|
|
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
341
|
if (this.isEarlyFlakeDetectionEnabled) {
|
|
324
342
|
const knownTestsResponse = await getKnownTests(
|
|
325
343
|
this.tracer,
|
|
@@ -465,12 +483,16 @@ class CypressPlugin {
|
|
|
465
483
|
const isSkippedByItr = this.testsToSkip.find(test =>
|
|
466
484
|
cypressTestName === test.name && spec.relative === test.suite
|
|
467
485
|
)
|
|
468
|
-
|
|
486
|
+
let testSourceFile
|
|
487
|
+
|
|
469
488
|
if (spec.absolute && this.repositoryRoot) {
|
|
470
|
-
|
|
489
|
+
testSourceFile = getTestSuitePath(spec.absolute, this.repositoryRoot)
|
|
471
490
|
} else {
|
|
472
|
-
|
|
491
|
+
testSourceFile = spec.relative
|
|
473
492
|
}
|
|
493
|
+
|
|
494
|
+
const skippedTestSpan = this.getTestSpan({ testName: cypressTestName, testSuite: spec.relative, testSourceFile })
|
|
495
|
+
|
|
474
496
|
skippedTestSpan.setTag(TEST_STATUS, 'skip')
|
|
475
497
|
if (isSkippedByItr) {
|
|
476
498
|
skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
|
|
@@ -485,29 +507,61 @@ class CypressPlugin {
|
|
|
485
507
|
// This is not always the case, such as when an `after` hook fails:
|
|
486
508
|
// Cypress will report the last run test as failed, but we don't know that yet at `dd:afterEach`
|
|
487
509
|
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)
|
|
510
|
+
|
|
511
|
+
const finishedTestsByTestName = finishedTests.reduce((acc, finishedTest) => {
|
|
512
|
+
if (!acc[finishedTest.testName]) {
|
|
513
|
+
acc[finishedTest.testName] = []
|
|
509
514
|
}
|
|
510
|
-
finishedTest.
|
|
515
|
+
acc[finishedTest.testName].push(finishedTest)
|
|
516
|
+
return acc
|
|
517
|
+
}, {})
|
|
518
|
+
|
|
519
|
+
Object.entries(finishedTestsByTestName).forEach(([testName, finishedTestAttempts]) => {
|
|
520
|
+
finishedTestAttempts.forEach((finishedTest, attemptIndex) => {
|
|
521
|
+
// TODO: there could be multiple if there have been retries!
|
|
522
|
+
// potentially we need to match the test status!
|
|
523
|
+
const cypressTest = cypressTests.find(test => test.title.join(' ') === testName)
|
|
524
|
+
if (!cypressTest) {
|
|
525
|
+
return
|
|
526
|
+
}
|
|
527
|
+
// finishedTests can include multiple tests with the same name if they have been retried
|
|
528
|
+
// by early flake detection. Cypress is unaware of this so .attempts does not necessarily have
|
|
529
|
+
// the same length as `finishedTestAttempts`
|
|
530
|
+
let cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
|
|
531
|
+
if (cypressTest.attempts && cypressTest.attempts[attemptIndex]) {
|
|
532
|
+
cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.attempts[attemptIndex].state]
|
|
533
|
+
if (attemptIndex > 0) {
|
|
534
|
+
finishedTest.testSpan.setTag(TEST_IS_RETRY, 'true')
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (cypressTest.displayError) {
|
|
538
|
+
latestError = new Error(cypressTest.displayError)
|
|
539
|
+
}
|
|
540
|
+
// Update test status
|
|
541
|
+
if (cypressTestStatus !== finishedTest.testStatus) {
|
|
542
|
+
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
|
|
543
|
+
finishedTest.testSpan.setTag('error', latestError)
|
|
544
|
+
}
|
|
545
|
+
if (this.itrCorrelationId) {
|
|
546
|
+
finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
|
|
547
|
+
}
|
|
548
|
+
let testSourceFile
|
|
549
|
+
if (spec.absolute && this.repositoryRoot) {
|
|
550
|
+
testSourceFile = getTestSuitePath(spec.absolute, this.repositoryRoot)
|
|
551
|
+
} else {
|
|
552
|
+
testSourceFile = spec.relative
|
|
553
|
+
}
|
|
554
|
+
if (testSourceFile) {
|
|
555
|
+
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, testSourceFile)
|
|
556
|
+
}
|
|
557
|
+
const codeOwners = this.getTestCodeOwners({ testSuite: spec.relative, testSourceFile })
|
|
558
|
+
|
|
559
|
+
if (codeOwners) {
|
|
560
|
+
finishedTest.testSpan.setTag(TEST_CODE_OWNERS, codeOwners)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
finishedTest.testSpan.finish(finishedTest.finishTime)
|
|
564
|
+
})
|
|
511
565
|
})
|
|
512
566
|
|
|
513
567
|
if (this.testSuiteSpan) {
|
|
@@ -554,7 +608,12 @@ class CypressPlugin {
|
|
|
554
608
|
}
|
|
555
609
|
|
|
556
610
|
if (!this.activeTestSpan) {
|
|
557
|
-
this.activeTestSpan = this.getTestSpan(
|
|
611
|
+
this.activeTestSpan = this.getTestSpan({
|
|
612
|
+
testName,
|
|
613
|
+
testSuite,
|
|
614
|
+
isUnskippable,
|
|
615
|
+
isForcedToRun
|
|
616
|
+
})
|
|
558
617
|
}
|
|
559
618
|
|
|
560
619
|
return this.activeTestSpan ? { traceId: this.activeTestSpan.context().toTraceId() } : {}
|
|
@@ -621,6 +680,13 @@ class CypressPlugin {
|
|
|
621
680
|
}
|
|
622
681
|
}
|
|
623
682
|
}
|
|
683
|
+
|
|
684
|
+
getTestCodeOwners ({ testSuite, testSourceFile }) {
|
|
685
|
+
if (testSourceFile) {
|
|
686
|
+
return getCodeOwnersForFilename(testSourceFile, this.codeOwnersEntries)
|
|
687
|
+
}
|
|
688
|
+
return getCodeOwnersForFilename(testSuite, this.codeOwnersEntries)
|
|
689
|
+
}
|
|
624
690
|
}
|
|
625
691
|
|
|
626
692
|
module.exports = new CypressPlugin()
|
|
@@ -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
|
})
|
|
@@ -91,7 +102,8 @@ class VitestPlugin extends CiPlugin {
|
|
|
91
102
|
).finish()
|
|
92
103
|
})
|
|
93
104
|
|
|
94
|
-
this.addSub('ci:vitest:test-suite:start', (testSuiteAbsolutePath) => {
|
|
105
|
+
this.addSub('ci:vitest:test-suite:start', ({ testSuiteAbsolutePath, frameworkVersion }) => {
|
|
106
|
+
this.frameworkVersion = frameworkVersion
|
|
95
107
|
const testSessionSpanContext = this.tracer.extract('text_map', {
|
|
96
108
|
'x-datadog-trace-id': process.env.DD_CIVISIBILITY_TEST_SESSION_ID,
|
|
97
109
|
'x-datadog-parent-id': process.env.DD_CIVISIBILITY_TEST_MODULE_ID
|
|
@@ -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)
|
|
@@ -9,6 +9,8 @@ let templateHtml = blockedTemplates.html
|
|
|
9
9
|
let templateJson = blockedTemplates.json
|
|
10
10
|
let templateGraphqlJson = blockedTemplates.graphqlJson
|
|
11
11
|
|
|
12
|
+
const responseBlockedSet = new WeakSet()
|
|
13
|
+
|
|
12
14
|
const specificBlockingTypes = {
|
|
13
15
|
GRAPHQL: 'graphql'
|
|
14
16
|
}
|
|
@@ -117,6 +119,8 @@ function block (req, res, rootSpan, abortController, actionParameters) {
|
|
|
117
119
|
|
|
118
120
|
res.writeHead(statusCode, headers).end(body)
|
|
119
121
|
|
|
122
|
+
responseBlockedSet.add(res)
|
|
123
|
+
|
|
120
124
|
abortController?.abort()
|
|
121
125
|
}
|
|
122
126
|
|
|
@@ -144,11 +148,16 @@ function setTemplates (config) {
|
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
|
|
151
|
+
function isBlocked (res) {
|
|
152
|
+
return responseBlockedSet.has(res)
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
module.exports = {
|
|
148
156
|
addSpecificEndpoint,
|
|
149
157
|
block,
|
|
150
158
|
specificBlockingTypes,
|
|
151
159
|
getBlockingData,
|
|
152
160
|
getBlockingAction,
|
|
153
|
-
setTemplates
|
|
161
|
+
setTemplates,
|
|
162
|
+
isBlocked
|
|
154
163
|
}
|
|
@@ -19,5 +19,8 @@ module.exports = {
|
|
|
19
19
|
nextQueryParsed: dc.channel('apm:next:query-parsed'),
|
|
20
20
|
responseBody: dc.channel('datadog:express:response:json:start'),
|
|
21
21
|
responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
|
|
22
|
-
httpClientRequestStart: dc.channel('apm:http:client:request:start')
|
|
22
|
+
httpClientRequestStart: dc.channel('apm:http:client:request:start'),
|
|
23
|
+
responseSetHeader: dc.channel('datadog:http:server:response:set-header:start'),
|
|
24
|
+
setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start')
|
|
25
|
+
|
|
23
26
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
+
CODE_INJECTION_ANALYZER: require('./code-injection-analyzer'),
|
|
4
5
|
COMMAND_INJECTION_ANALYZER: require('./command-injection-analyzer'),
|
|
5
6
|
HARCODED_PASSWORD_ANALYZER: require('./hardcoded-password-analyzer'),
|
|
6
7
|
HARCODED_SECRET_ANALYZER: require('./hardcoded-secret-analyzer'),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { CODE_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
|
|
6
|
+
class CodeInjectionAnalyzer extends InjectionAnalyzer {
|
|
7
|
+
constructor () {
|
|
8
|
+
super(CODE_INJECTION)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
onConfigure () {
|
|
12
|
+
this.addSub('datadog:eval:call', ({ script }) => this.analyze(script))
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = new CodeInjectionAnalyzer()
|
|
@@ -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]
|
|
@@ -14,7 +14,8 @@ const csiMethods = [
|
|
|
14
14
|
{ src: 'toUpperCase', dst: 'stringCase' },
|
|
15
15
|
{ src: 'trim' },
|
|
16
16
|
{ src: 'trimEnd' },
|
|
17
|
-
{ src: 'trimStart', dst: 'trim' }
|
|
17
|
+
{ src: 'trimStart', dst: 'trim' },
|
|
18
|
+
{ src: 'eval', allowedWithoutCallee: true }
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
module.exports = {
|
|
@@ -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
|
|
|
@@ -10,6 +10,7 @@ const { isDebugAllowed } = require('../telemetry/verbosity')
|
|
|
10
10
|
const { taintObject } = require('./operations-taint-object')
|
|
11
11
|
|
|
12
12
|
const mathRandomCallCh = dc.channel('datadog:random:call')
|
|
13
|
+
const evalCallCh = dc.channel('datadog:eval:call')
|
|
13
14
|
|
|
14
15
|
const JSON_VALUE = 'json.value'
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ function noop (res) { return res }
|
|
|
18
19
|
// Otherwise you may end up rewriting a method and not providing its rewritten implementation
|
|
19
20
|
const TaintTrackingNoop = {
|
|
20
21
|
concat: noop,
|
|
22
|
+
eval: noop,
|
|
21
23
|
join: noop,
|
|
22
24
|
parse: noop,
|
|
23
25
|
plusOperator: noop,
|
|
@@ -136,6 +138,15 @@ function csiMethodsOverrides (getContext) {
|
|
|
136
138
|
return res
|
|
137
139
|
},
|
|
138
140
|
|
|
141
|
+
eval: function (res, fn, target, script) {
|
|
142
|
+
// eslint-disable-next-line no-eval
|
|
143
|
+
if (evalCallCh.hasSubscribers && fn === globalThis.eval) {
|
|
144
|
+
evalCallCh.publish({ script })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return res
|
|
148
|
+
},
|
|
149
|
+
|
|
139
150
|
parse: function (res, fn, target, json) {
|
|
140
151
|
if (fn === JSON.parse) {
|
|
141
152
|
try {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = function extractSensitiveRanges (evidence) {
|
|
4
|
+
const newRanges = []
|
|
5
|
+
if (evidence.ranges[0].start > 0) {
|
|
6
|
+
newRanges.push({
|
|
7
|
+
start: 0,
|
|
8
|
+
end: evidence.ranges[0].start
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < evidence.ranges.length; i++) {
|
|
13
|
+
const currentRange = evidence.ranges[i]
|
|
14
|
+
const nextRange = evidence.ranges[i + 1]
|
|
15
|
+
|
|
16
|
+
const start = currentRange.end
|
|
17
|
+
const end = nextRange?.start || evidence.value.length
|
|
18
|
+
|
|
19
|
+
if (start < end) {
|
|
20
|
+
newRanges.push({ start, end })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return newRanges
|
|
25
|
+
}
|