dd-trace 5.87.0 → 5.89.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 +60 -32
- package/ext/exporters.d.ts +1 -0
- package/ext/exporters.js +1 -0
- package/ext/tags.js +2 -0
- package/index.d.ts +234 -4
- package/package.json +18 -11
- package/packages/datadog-instrumentations/src/ai.js +54 -90
- package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +27 -110
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/compiler.js +74 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/index.js +43 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/matcher.js +49 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/transformer.js +121 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/{transforms.js → orchestrion/transforms.js} +143 -17
- package/packages/datadog-instrumentations/src/jest.js +176 -54
- package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
- package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
- package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
- package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
- package/packages/datadog-plugin-cucumber/src/index.js +9 -6
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +62 -5
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +297 -0
- package/packages/datadog-plugin-cypress/src/support.js +52 -9
- package/packages/datadog-plugin-jest/src/index.js +12 -2
- package/packages/datadog-plugin-jest/src/util.js +2 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
- package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
- package/packages/datadog-plugin-mocha/src/index.js +9 -6
- package/packages/datadog-plugin-playwright/src/index.js +10 -6
- package/packages/datadog-plugin-vitest/src/index.js +13 -8
- package/packages/dd-trace/src/aiguard/sdk.js +5 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
- package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
- 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/utils.js +3 -2
- package/packages/dd-trace/src/azure_metadata.js +0 -2
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +3 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +148 -197
- package/packages/dd-trace/src/config/helper.js +43 -1
- package/packages/dd-trace/src/config/index.js +38 -14
- package/packages/dd-trace/src/config/supported-configurations.json +4125 -512
- package/packages/dd-trace/src/constants.js +0 -2
- package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
- package/packages/dd-trace/src/datastreams/checkpointer.js +13 -0
- package/packages/dd-trace/src/datastreams/index.js +3 -0
- package/packages/dd-trace/src/datastreams/manager.js +9 -0
- package/packages/dd-trace/src/datastreams/pathway.js +22 -3
- package/packages/dd-trace/src/datastreams/processor.js +140 -4
- package/packages/dd-trace/src/encode/agentless-json.js +155 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/agent/writer.js +21 -8
- package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
- package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
- package/packages/dd-trace/src/exporters/common/request.js +4 -4
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
- package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
- package/packages/dd-trace/src/opentracing/span.js +6 -4
- package/packages/dd-trace/src/pkg.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
- package/packages/dd-trace/src/plugins/database.js +15 -2
- package/packages/dd-trace/src/plugins/util/test.js +48 -0
- package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
- package/packages/dd-trace/src/propagation-hash/index.js +145 -0
- package/packages/dd-trace/src/proxy.js +6 -1
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/startup-log.js +53 -19
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/@datadog/source-map/index.js +1 -1
- package/vendor/dist/@isaacs/ttlcache/index.js +1 -1
- package/vendor/dist/@opentelemetry/core/index.js +1 -1
- package/vendor/dist/@opentelemetry/resources/index.js +1 -1
- package/vendor/dist/astring/index.js +1 -1
- package/vendor/dist/crypto-randomuuid/index.js +1 -1
- package/vendor/dist/escape-string-regexp/index.js +1 -1
- package/vendor/dist/esquery/index.js +1 -1
- package/vendor/dist/ignore/index.js +1 -1
- package/vendor/dist/istanbul-lib-coverage/index.js +1 -1
- package/vendor/dist/jest-docblock/index.js +1 -1
- package/vendor/dist/jsonpath-plus/index.js +1 -1
- package/vendor/dist/limiter/index.js +1 -1
- package/vendor/dist/lodash.sortby/index.js +1 -1
- package/vendor/dist/lru-cache/index.js +1 -1
- package/vendor/dist/meriyah/index.js +1 -1
- package/vendor/dist/module-details-from-path/index.js +1 -1
- package/vendor/dist/mutexify/promise/index.js +1 -1
- package/vendor/dist/opentracing/index.js +1 -1
- package/vendor/dist/path-to-regexp/index.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/retry/index.js +1 -1
- package/vendor/dist/rfdc/index.js +1 -1
- package/vendor/dist/semifies/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
- package/vendor/dist/source-map/lib/util/index.js +1 -1
- package/vendor/dist/tlhunter-sorted-set/index.js +1 -1
- package/vendor/dist/ttl-set/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +0 -33
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
- package/packages/dd-trace/src/scope/noop/scope.js +0 -21
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
|
|
4
|
+
let rumFlushWaitMillis = 500
|
|
5
|
+
|
|
3
6
|
let isEarlyFlakeDetectionEnabled = false
|
|
4
7
|
let isKnownTestsEnabled = false
|
|
5
8
|
let knownTestsForSuite = []
|
|
@@ -15,9 +18,10 @@ const retryReasonsByTestName = new Map()
|
|
|
15
18
|
// Track quarantined test errors - we catch them in Cypress.on('fail') but need to report to Datadog
|
|
16
19
|
const quarantinedTestErrors = new Map()
|
|
17
20
|
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
+
// Track the most recently loaded window in the AUT. Updated via the 'window:load'
|
|
22
|
+
// event so we always get the real app window (after cy.visit()), not the
|
|
23
|
+
// about:blank window that exists when beforeEach runs. If the test later navigates
|
|
24
|
+
// to a cross-origin URL, safeGetRum() handles the access error.
|
|
21
25
|
let originalWindow
|
|
22
26
|
|
|
23
27
|
// If the test is using multi domain with cy.origin, trying to access
|
|
@@ -165,18 +169,45 @@ beforeEach(function () {
|
|
|
165
169
|
retryReasonsByTestName.delete(testName)
|
|
166
170
|
}
|
|
167
171
|
|
|
172
|
+
cy.on('window:load', (win) => {
|
|
173
|
+
originalWindow = win
|
|
174
|
+
})
|
|
175
|
+
|
|
168
176
|
cy.task('dd:beforeEach', {
|
|
169
177
|
testName,
|
|
170
178
|
testSuite: Cypress.mocha.getRootSuite().file,
|
|
171
179
|
}).then(({ traceId, shouldSkip }) => {
|
|
172
|
-
|
|
180
|
+
if (traceId) {
|
|
181
|
+
cy.setCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME, traceId).then(() => {
|
|
182
|
+
// When testIsolation:false, the page is not reset between tests, so the RUM session
|
|
183
|
+
// stopped in afterEach must be explicitly restarted so events in this test are
|
|
184
|
+
// associated with the new testExecutionId.
|
|
185
|
+
//
|
|
186
|
+
// After stopSession(), the RUM SDK creates a new session upon a user interaction
|
|
187
|
+
// (click, scroll, keydown, or touchstart). We dispatch a synthetic click on the window
|
|
188
|
+
// to trigger session renewal, then call startView() to establish a view boundary.
|
|
189
|
+
if (!isTestIsolationEnabled && originalWindow) {
|
|
190
|
+
const rum = safeGetRum(originalWindow)
|
|
191
|
+
if (rum) {
|
|
192
|
+
try {
|
|
193
|
+
const evt = new originalWindow.MouseEvent('click', { bubbles: true, cancelable: true })
|
|
194
|
+
// The browser-sdk addEventListener wrapper filters out untrusted synthetic events
|
|
195
|
+
// unless __ddIsTrusted is set. Set it so the click triggers expandOrRenewSession().
|
|
196
|
+
// See: https://github.com/DataDog/browser-sdk/blob/v6.27.1/packages/core/src/browser/addEventListener.ts#L119
|
|
197
|
+
Object.defineProperty(evt, '__ddIsTrusted', { value: true })
|
|
198
|
+
originalWindow.dispatchEvent(evt)
|
|
199
|
+
} catch {}
|
|
200
|
+
if (rum.startView) {
|
|
201
|
+
rum.startView()
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
173
207
|
if (shouldSkip) {
|
|
174
208
|
this.skip()
|
|
175
209
|
}
|
|
176
210
|
})
|
|
177
|
-
cy.window().then(win => {
|
|
178
|
-
originalWindow = win
|
|
179
|
-
})
|
|
180
211
|
})
|
|
181
212
|
|
|
182
213
|
before(function () {
|
|
@@ -195,6 +226,9 @@ before(function () {
|
|
|
195
226
|
isImpactedTestsEnabled = suiteConfig.isImpactedTestsEnabled
|
|
196
227
|
isModifiedTest = suiteConfig.isModifiedTest
|
|
197
228
|
isTestIsolationEnabled = suiteConfig.isTestIsolationEnabled
|
|
229
|
+
if (Number.isFinite(suiteConfig.rumFlushWaitMillis)) {
|
|
230
|
+
rumFlushWaitMillis = suiteConfig.rumFlushWaitMillis
|
|
231
|
+
}
|
|
198
232
|
}
|
|
199
233
|
})
|
|
200
234
|
})
|
|
@@ -224,6 +258,7 @@ afterEach(function () {
|
|
|
224
258
|
|
|
225
259
|
const testInfo = {
|
|
226
260
|
testName,
|
|
261
|
+
testItTitle: currentTest.title,
|
|
227
262
|
testSuite: Cypress.mocha.getRootSuite().file,
|
|
228
263
|
testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute,
|
|
229
264
|
// For quarantined tests, report the actual state (failed) to Datadog, not what Cypress thinks (passed)
|
|
@@ -238,11 +273,19 @@ afterEach(function () {
|
|
|
238
273
|
isQuarantined: isQuarantinedTestThatFailed,
|
|
239
274
|
}
|
|
240
275
|
try {
|
|
241
|
-
|
|
276
|
+
const invocationDetails = Cypress.mocha.getRunner().currentRunnable.invocationDetails
|
|
277
|
+
testInfo.testSourceLine = invocationDetails.line
|
|
278
|
+
testInfo.testSourceStack = invocationDetails.stack
|
|
242
279
|
} catch {}
|
|
243
280
|
|
|
244
|
-
|
|
281
|
+
const rum = safeGetRum(originalWindow)
|
|
282
|
+
if (rum) {
|
|
245
283
|
testInfo.isRUMActive = true
|
|
284
|
+
if (rum.stopSession) {
|
|
285
|
+
rum.stopSession()
|
|
286
|
+
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
|
287
|
+
cy.wait(rumFlushWaitMillis)
|
|
288
|
+
}
|
|
246
289
|
}
|
|
247
290
|
let coverage
|
|
248
291
|
try {
|
|
@@ -182,9 +182,10 @@ class JestPlugin extends CiPlugin {
|
|
|
182
182
|
config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
|
|
183
183
|
config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
|
|
184
184
|
config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
|
|
185
|
+
config._ddRequestErrorTags = this.getSessionRequestErrorTags()
|
|
185
186
|
config._ddItrCorrelationId = this.itrCorrelationId
|
|
186
187
|
config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
|
|
187
|
-
config.
|
|
188
|
+
config._ddEarlyFlakeDetectionSlowTestRetries = this.libraryConfig?.earlyFlakeDetectionSlowTestRetries ?? {}
|
|
188
189
|
config._ddRepositoryRoot = this.repositoryRoot
|
|
189
190
|
config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
|
|
190
191
|
config._ddIsTestManagementTestsEnabled = this.libraryConfig?.isTestManagementEnabled ?? false
|
|
@@ -208,6 +209,7 @@ class JestPlugin extends CiPlugin {
|
|
|
208
209
|
_ddTestSessionId: testSessionId,
|
|
209
210
|
_ddTestCommand: testCommand,
|
|
210
211
|
_ddTestModuleId: testModuleId,
|
|
212
|
+
_ddRequestErrorTags: requestErrorTags,
|
|
211
213
|
_ddItrCorrelationId: itrCorrelationId,
|
|
212
214
|
_ddForcedToRun,
|
|
213
215
|
_ddUnskippable,
|
|
@@ -219,7 +221,11 @@ class JestPlugin extends CiPlugin {
|
|
|
219
221
|
'x-datadog-parent-id': testModuleId,
|
|
220
222
|
})
|
|
221
223
|
|
|
222
|
-
const testSuiteMetadata =
|
|
224
|
+
const testSuiteMetadata = {
|
|
225
|
+
...getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest'),
|
|
226
|
+
// requestErrorTags from test env options may be undefined
|
|
227
|
+
...(requestErrorTags !== undefined && requestErrorTags !== null ? requestErrorTags : {}),
|
|
228
|
+
}
|
|
223
229
|
|
|
224
230
|
if (_ddUnskippable) {
|
|
225
231
|
const unskippableSuites = this.getUnskippableSuites(_ddUnskippable)
|
|
@@ -389,6 +395,7 @@ class JestPlugin extends CiPlugin {
|
|
|
389
395
|
attemptToFixFailed,
|
|
390
396
|
isAtrRetry,
|
|
391
397
|
finalStatus,
|
|
398
|
+
earlyFlakeAbortReason,
|
|
392
399
|
}) => {
|
|
393
400
|
span.setTag(TEST_STATUS, status)
|
|
394
401
|
if (finalStatus) {
|
|
@@ -409,6 +416,9 @@ class JestPlugin extends CiPlugin {
|
|
|
409
416
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
410
417
|
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
|
|
411
418
|
}
|
|
419
|
+
if (earlyFlakeAbortReason) {
|
|
420
|
+
span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
|
|
421
|
+
}
|
|
412
422
|
|
|
413
423
|
this.telemetry.ciVisEvent(
|
|
414
424
|
TELEMETRY_EVENT_FINISHED,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { readFileSync } = require('fs')
|
|
4
4
|
const { parse } = require('../../../vendor/dist/jest-docblock')
|
|
5
5
|
|
|
6
|
-
const { getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
|
|
6
|
+
const { getTestSuitePath, getEfdRetryCount } = require('../../dd-trace/src/plugins/util/test')
|
|
7
7
|
const log = require('../../dd-trace/src/log')
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -172,4 +172,5 @@ module.exports = {
|
|
|
172
172
|
getJestTestName,
|
|
173
173
|
getJestSuitesToRun,
|
|
174
174
|
isMarkedAsUnskippable,
|
|
175
|
+
getEfdRetryCount,
|
|
175
176
|
}
|
|
@@ -40,14 +40,18 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
|
|
|
40
40
|
* @returns {ConsumerBacklog}
|
|
41
41
|
*/
|
|
42
42
|
transformCommit (commit) {
|
|
43
|
-
const { groupId, partition, offset, topic } = commit
|
|
44
|
-
|
|
43
|
+
const { groupId, partition, offset, topic, clusterId } = commit
|
|
44
|
+
const backlog = {
|
|
45
45
|
partition,
|
|
46
46
|
topic,
|
|
47
47
|
type: 'kafka_commit',
|
|
48
48
|
offset: Number(offset),
|
|
49
49
|
consumer_group: groupId,
|
|
50
50
|
}
|
|
51
|
+
if (clusterId) {
|
|
52
|
+
backlog.kafka_cluster_id = clusterId
|
|
53
|
+
}
|
|
54
|
+
return backlog
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
commit (commitList) {
|
|
@@ -65,6 +69,22 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
|
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
start (ctx) {
|
|
73
|
+
if (!this.config.dsmEnabled) return
|
|
74
|
+
const { topic, message, groupId, clusterId } = ctx.extractedArgs || ctx
|
|
75
|
+
const headers = convertToTextMap(message?.headers)
|
|
76
|
+
if (!headers) return
|
|
77
|
+
|
|
78
|
+
const { span } = ctx.currentStore
|
|
79
|
+
const payloadSize = getMessageSize(message)
|
|
80
|
+
this.tracer.decodeDataStreamsContext(headers)
|
|
81
|
+
const edgeTags = ['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka']
|
|
82
|
+
if (clusterId) {
|
|
83
|
+
edgeTags.push(`kafka_cluster_id:${clusterId}`)
|
|
84
|
+
}
|
|
85
|
+
this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
bindStart (ctx) {
|
|
69
89
|
const { topic, partition, message, groupId, clusterId } = ctx.extractedArgs || ctx
|
|
70
90
|
|
|
@@ -89,16 +109,6 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
|
|
|
89
109
|
}, ctx)
|
|
90
110
|
if (message?.offset) span.setTag('kafka.message.offset', message?.offset)
|
|
91
111
|
|
|
92
|
-
if (this.config.dsmEnabled && headers) {
|
|
93
|
-
const payloadSize = getMessageSize(message)
|
|
94
|
-
this.tracer.decodeDataStreamsContext(headers)
|
|
95
|
-
const edgeTags = ['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka']
|
|
96
|
-
if (clusterId) {
|
|
97
|
-
edgeTags.push(`kafka_cluster_id:${clusterId}`)
|
|
98
|
-
}
|
|
99
|
-
this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
112
|
if (afterStartCh.hasSubscribers) {
|
|
103
113
|
afterStartCh.publish({ topic, partition, message, groupId, currentStore: ctx.currentStore })
|
|
104
114
|
}
|
|
@@ -37,16 +37,20 @@ class KafkajsProducerPlugin extends ProducerPlugin {
|
|
|
37
37
|
* @param {ProducerResponseItem} response
|
|
38
38
|
* @returns {ProducerBacklog}
|
|
39
39
|
*/
|
|
40
|
-
transformProduceResponse (response) {
|
|
40
|
+
transformProduceResponse (response, clusterId) {
|
|
41
41
|
// In produce protocol >=v3, the offset key changes from `offset` to `baseOffset`
|
|
42
42
|
const { topicName: topic, partition, offset, baseOffset } = response
|
|
43
43
|
const offsetAsLong = offset || baseOffset
|
|
44
|
-
|
|
44
|
+
const backlog = {
|
|
45
45
|
type: 'kafka_produce',
|
|
46
46
|
partition,
|
|
47
47
|
offset: offsetAsLong ? Number(offsetAsLong) : undefined,
|
|
48
48
|
topic,
|
|
49
49
|
}
|
|
50
|
+
if (clusterId) {
|
|
51
|
+
backlog.kafka_cluster_id = clusterId
|
|
52
|
+
}
|
|
53
|
+
return backlog
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
/**
|
|
@@ -56,6 +60,7 @@ class KafkajsProducerPlugin extends ProducerPlugin {
|
|
|
56
60
|
*/
|
|
57
61
|
commit (ctx) {
|
|
58
62
|
const commitList = ctx.result
|
|
63
|
+
const clusterId = ctx.clusterId
|
|
59
64
|
|
|
60
65
|
if (!this.config.dsmEnabled) return
|
|
61
66
|
if (!commitList || !Array.isArray(commitList)) return
|
|
@@ -65,12 +70,33 @@ class KafkajsProducerPlugin extends ProducerPlugin {
|
|
|
65
70
|
'offset',
|
|
66
71
|
'topic',
|
|
67
72
|
]
|
|
68
|
-
for (const commit of commitList.map(this.transformProduceResponse)) {
|
|
73
|
+
for (const commit of commitList.map(r => this.transformProduceResponse(r, clusterId))) {
|
|
69
74
|
if (keys.some(key => !commit.hasOwnProperty(key))) continue
|
|
70
75
|
this.tracer.setOffset(commit)
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
|
|
79
|
+
start (ctx) {
|
|
80
|
+
if (!this.config.dsmEnabled) return
|
|
81
|
+
const { topic, messages, clusterId, disableHeaderInjection, currentStore: { span } } = ctx
|
|
82
|
+
|
|
83
|
+
for (const message of messages) {
|
|
84
|
+
if (message !== null && typeof message === 'object') {
|
|
85
|
+
const payloadSize = getMessageSize(message)
|
|
86
|
+
const edgeTags = ['direction:out', `topic:${topic}`, 'type:kafka']
|
|
87
|
+
|
|
88
|
+
if (clusterId) {
|
|
89
|
+
edgeTags.push(`kafka_cluster_id:${clusterId}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
93
|
+
if (!disableHeaderInjection) {
|
|
94
|
+
DsmPathwayCodec.encode(dataStreamsContext, message.headers)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
74
100
|
bindStart (ctx) {
|
|
75
101
|
const { topic, messages, bootstrapServers, clusterId, disableHeaderInjection } = ctx
|
|
76
102
|
const span = this.startSpan({
|
|
@@ -89,25 +115,10 @@ class KafkajsProducerPlugin extends ProducerPlugin {
|
|
|
89
115
|
span.setTag(BOOTSTRAP_SERVERS_KEY, bootstrapServers)
|
|
90
116
|
}
|
|
91
117
|
for (const message of messages) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this.tracer.inject(span, 'text_map', message.headers)
|
|
97
|
-
}
|
|
98
|
-
if (this.config.dsmEnabled) {
|
|
99
|
-
const payloadSize = getMessageSize(message)
|
|
100
|
-
const edgeTags = ['direction:out', `topic:${topic}`, 'type:kafka']
|
|
101
|
-
|
|
102
|
-
if (clusterId) {
|
|
103
|
-
edgeTags.push(`kafka_cluster_id:${clusterId}`)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
107
|
-
if (!disableHeaderInjection) {
|
|
108
|
-
DsmPathwayCodec.encode(dataStreamsContext, message.headers)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
118
|
+
// message headers are not supported for kafka broker versions <0.11
|
|
119
|
+
if (message !== null && typeof message === 'object' && !disableHeaderInjection) {
|
|
120
|
+
message.headers ??= {}
|
|
121
|
+
this.tracer.inject(span, 'text_map', message.headers)
|
|
111
122
|
}
|
|
112
123
|
}
|
|
113
124
|
|
|
@@ -93,12 +93,15 @@ class MochaPlugin extends CiPlugin {
|
|
|
93
93
|
return
|
|
94
94
|
}
|
|
95
95
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
|
|
96
|
-
const testSuiteMetadata =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
const testSuiteMetadata = {
|
|
97
|
+
...getTestSuiteCommonTags(
|
|
98
|
+
this.command,
|
|
99
|
+
this.frameworkVersion,
|
|
100
|
+
testSuite,
|
|
101
|
+
'mocha'
|
|
102
|
+
),
|
|
103
|
+
...this.getSessionRequestErrorTags(),
|
|
104
|
+
}
|
|
102
105
|
if (isUnskippable) {
|
|
103
106
|
testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
104
107
|
this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
|
|
@@ -120,12 +120,15 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
120
120
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
121
121
|
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
122
122
|
|
|
123
|
-
const testSuiteMetadata =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
const testSuiteMetadata = {
|
|
124
|
+
...getTestSuiteCommonTags(
|
|
125
|
+
this.command,
|
|
126
|
+
this.frameworkVersion,
|
|
127
|
+
testSuite,
|
|
128
|
+
'playwright'
|
|
129
|
+
),
|
|
130
|
+
...this.getSessionRequestErrorTags(),
|
|
131
|
+
}
|
|
129
132
|
if (testSourceFile) {
|
|
130
133
|
testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
|
|
131
134
|
testSuiteMetadata[TEST_SOURCE_START] = 1
|
|
@@ -222,6 +225,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
222
225
|
// for a test session. They can be passed the same way `DD_PLAYWRIGHT_WORKER` is passed.
|
|
223
226
|
formattedSpan.meta[TEST_SESSION_ID] = this.testSessionSpan.context().toTraceId()
|
|
224
227
|
formattedSpan.meta[TEST_MODULE_ID] = this.testModuleSpan.context().toSpanId()
|
|
228
|
+
Object.assign(formattedSpan.meta, this.getSessionRequestErrorTags())
|
|
225
229
|
formattedSpan.meta[TEST_COMMAND] = this.command
|
|
226
230
|
formattedSpan.meta[TEST_MODULE] = this.constructor.id
|
|
227
231
|
// MISSING _trace.startTime and _trace.ticks - because by now the suite is already serialized
|
|
@@ -275,9 +275,11 @@ class VitestPlugin extends CiPlugin {
|
|
|
275
275
|
this.addBind('ci:vitest:test-suite:start', (ctx) => {
|
|
276
276
|
const { testSuiteAbsolutePath, frameworkVersion } = ctx
|
|
277
277
|
|
|
278
|
+
// TODO: Handle case where the command is not set
|
|
278
279
|
this.command = getValueFromEnvSources('DD_CIVISIBILITY_TEST_COMMAND')
|
|
279
280
|
this.frameworkVersion = frameworkVersion
|
|
280
281
|
const testSessionSpanContext = this.tracer.extract('text_map', {
|
|
282
|
+
// TODO: Handle case where the session ID or module ID is not set
|
|
281
283
|
'x-datadog-trace-id': getValueFromEnvSources('DD_CIVISIBILITY_TEST_SESSION_ID'),
|
|
282
284
|
'x-datadog-parent-id': getValueFromEnvSources('DD_CIVISIBILITY_TEST_MODULE_ID'),
|
|
283
285
|
})
|
|
@@ -301,14 +303,17 @@ class VitestPlugin extends CiPlugin {
|
|
|
301
303
|
}
|
|
302
304
|
|
|
303
305
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
306
|
+
// Request error tags are applied to test spans in the main process (worker-report:trace handler)
|
|
307
|
+
const testSuiteMetadata = {
|
|
308
|
+
...getTestSuiteCommonTags(
|
|
309
|
+
this.command,
|
|
310
|
+
this.frameworkVersion,
|
|
311
|
+
testSuite,
|
|
312
|
+
'vitest'
|
|
313
|
+
),
|
|
314
|
+
[TEST_SOURCE_FILE]: testSuite,
|
|
315
|
+
[TEST_SOURCE_START]: 1,
|
|
316
|
+
}
|
|
312
317
|
|
|
313
318
|
const codeOwners = this.getCodeOwners(testSuiteMetadata)
|
|
314
319
|
if (codeOwners) {
|
|
@@ -175,7 +175,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
175
175
|
`AI Guard service call failed, status ${response.status}`,
|
|
176
176
|
{ errors: response.body?.errors })
|
|
177
177
|
}
|
|
178
|
-
let action, reason, tags, blockingEnabled
|
|
178
|
+
let action, reason, tags, sdsFindings, blockingEnabled
|
|
179
179
|
try {
|
|
180
180
|
const attr = response.body.data.attributes
|
|
181
181
|
if (!attr.action) {
|
|
@@ -184,6 +184,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
184
184
|
action = attr.action
|
|
185
185
|
reason = attr.reason
|
|
186
186
|
tags = attr.tags
|
|
187
|
+
sdsFindings = attr.sds_findings
|
|
187
188
|
blockingEnabled = attr.is_blocking_enabled ?? false
|
|
188
189
|
} catch (e) {
|
|
189
190
|
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
|
|
@@ -198,6 +199,9 @@ class AIGuard extends NoopAIGuard {
|
|
|
198
199
|
if (tags?.length > 0) {
|
|
199
200
|
metaStruct.attack_categories = tags
|
|
200
201
|
}
|
|
202
|
+
if (sdsFindings?.length > 0) {
|
|
203
|
+
metaStruct.sds = sdsFindings
|
|
204
|
+
}
|
|
201
205
|
if (shouldBlock) {
|
|
202
206
|
span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
|
|
203
207
|
throw new AIGuardAbortError(reason, tags)
|
|
@@ -12,7 +12,7 @@ class SSRFAnalyzer extends InjectionAnalyzer {
|
|
|
12
12
|
this.addSub('apm:http:client:request:start', ({ args }) => {
|
|
13
13
|
if (typeof args.originalUrl === 'string') {
|
|
14
14
|
this.analyze(args.originalUrl)
|
|
15
|
-
} else if (args.options
|
|
15
|
+
} else if (args.options?.host) {
|
|
16
16
|
this.analyze(args.options.host)
|
|
17
17
|
}
|
|
18
18
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { storage } = require('../../../../../datadog-core')
|
|
4
|
-
const {
|
|
4
|
+
const { getCallSiteFramesForLocation } = require('../path-line')
|
|
5
5
|
const { getIastContext, getIastStackTraceId } = require('../iast-context')
|
|
6
6
|
const overheadController = require('../overhead-controller')
|
|
7
7
|
const { SinkIastPlugin } = require('../iast-plugin')
|
|
@@ -35,9 +35,8 @@ class Analyzer extends SinkIastPlugin {
|
|
|
35
35
|
|
|
36
36
|
_reportEvidence (value, context, evidence) {
|
|
37
37
|
const callSiteFrames = getVulnerabilityCallSiteFrames()
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const location = this._getLocation(value, nonDDCallSiteFrames)
|
|
38
|
+
const frames = getCallSiteFramesForLocation(callSiteFrames, this._getExcludedPaths())
|
|
39
|
+
const location = this._getLocation(value, frames)
|
|
41
40
|
|
|
42
41
|
if (!this._isExcluded(location)) {
|
|
43
42
|
const originalLocation = this._getOriginalLocation(location)
|
|
@@ -51,7 +50,7 @@ class Analyzer extends SinkIastPlugin {
|
|
|
51
50
|
stackId
|
|
52
51
|
)
|
|
53
52
|
|
|
54
|
-
addVulnerability(context, vulnerability,
|
|
53
|
+
addVulnerability(context, vulnerability, frames)
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
|
|
@@ -8,29 +8,40 @@ const { getOriginalPathAndLineFromSourceMap } = require('./taint-tracking/rewrit
|
|
|
8
8
|
const pathLine = {
|
|
9
9
|
getNodeModulesPaths,
|
|
10
10
|
getRelativePath,
|
|
11
|
-
|
|
11
|
+
getCallSiteFramesForLocation,
|
|
12
12
|
ddBasePath, // Exported only for test purposes
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const EXCLUDED_PATHS = [
|
|
16
16
|
path.join(path.sep, 'node_modules', 'dc-polyfill'),
|
|
17
17
|
]
|
|
18
|
-
const EXCLUDED_PATH_PREFIXES = [
|
|
19
|
-
'node:diagnostics_channel',
|
|
20
|
-
'diagnostics_channel',
|
|
21
|
-
'node:child_process',
|
|
22
|
-
'child_process',
|
|
23
|
-
'node:async_hooks',
|
|
24
|
-
'async_hooks',
|
|
25
|
-
'node:internal/async_local_storage',
|
|
26
|
-
]
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Processes and filters call site frames to find the best location for a vulnerability.
|
|
21
|
+
* Returns client frames if available, otherwise falls back to all processed frames.
|
|
22
|
+
* Excludes dd-trace frames and all Node.js built-in/internal modules (node:*).
|
|
23
|
+
* @param {CallSiteFrame[]} callSiteFrames
|
|
24
|
+
* @param {string[]} externallyExcludedPaths
|
|
25
|
+
* @returns {CallSiteFrame[]} Client frames if available, otherwise all processed frames
|
|
26
|
+
*
|
|
27
|
+
* @typedef {object} CallSiteFrame
|
|
28
|
+
* @property {number} id
|
|
29
|
+
* @property {string} file - Original file path
|
|
30
|
+
* @property {number} line
|
|
31
|
+
* @property {number} column
|
|
32
|
+
* @property {string} function
|
|
33
|
+
* @property {string} class_name
|
|
34
|
+
* @property {boolean} isNative
|
|
35
|
+
* @property {string} [path] - Relative path, added during processing
|
|
36
|
+
* @property {boolean} [isInternal] - Whether the frame is internal, added during processing
|
|
37
|
+
*/
|
|
38
|
+
function getCallSiteFramesForLocation (callSiteFrames, externallyExcludedPaths) {
|
|
29
39
|
if (!callSiteFrames) {
|
|
30
40
|
return []
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
const
|
|
43
|
+
const allFrames = []
|
|
44
|
+
const clientFrames = []
|
|
34
45
|
|
|
35
46
|
for (const callsite of callSiteFrames) {
|
|
36
47
|
let filepath = callsite.file?.startsWith('file://') ? fileURLToPath(callsite.file) : callsite.file
|
|
@@ -47,18 +58,22 @@ function getNonDDCallSiteFrames (callSiteFrames, externallyExcludedPaths) {
|
|
|
47
58
|
callsite.column = column
|
|
48
59
|
}
|
|
49
60
|
|
|
50
|
-
if (
|
|
51
|
-
!isExcluded(callsite, externallyExcludedPaths) &&
|
|
52
|
-
(!filepath.includes(pathLine.ddBasePath) || globalThis.__DD_ESBUILD_IAST_WITH_NO_SM)
|
|
53
|
-
) {
|
|
61
|
+
if (filepath) {
|
|
54
62
|
callsite.path = getRelativePath(filepath)
|
|
55
63
|
callsite.isInternal = !path.isAbsolute(filepath)
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
allFrames.push(callsite)
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
!isExcluded(callsite, externallyExcludedPaths) &&
|
|
69
|
+
(!filepath.includes(pathLine.ddBasePath) || globalThis.__DD_ESBUILD_IAST_WITH_NO_SM)
|
|
70
|
+
) {
|
|
71
|
+
clientFrames.push(callsite)
|
|
72
|
+
}
|
|
58
73
|
}
|
|
59
74
|
}
|
|
60
75
|
|
|
61
|
-
return
|
|
76
|
+
return clientFrames.length > 0 ? clientFrames : allFrames
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
function getRelativePath (filepath) {
|
|
@@ -67,10 +82,12 @@ function getRelativePath (filepath) {
|
|
|
67
82
|
|
|
68
83
|
function isExcluded (callsite, externallyExcludedPaths) {
|
|
69
84
|
if (callsite.isNative) return true
|
|
85
|
+
|
|
70
86
|
const filename = globalThis.__DD_ESBUILD_IAST_WITH_SM ? callsite.path : callsite.file
|
|
71
|
-
if (!filename) {
|
|
87
|
+
if (!filename || filename.startsWith('node:')) {
|
|
72
88
|
return true
|
|
73
89
|
}
|
|
90
|
+
|
|
74
91
|
let excludedPaths = EXCLUDED_PATHS
|
|
75
92
|
if (externallyExcludedPaths) {
|
|
76
93
|
excludedPaths = [...excludedPaths, ...externallyExcludedPaths]
|
|
@@ -82,12 +99,6 @@ function isExcluded (callsite, externallyExcludedPaths) {
|
|
|
82
99
|
}
|
|
83
100
|
}
|
|
84
101
|
|
|
85
|
-
for (const EXCLUDED_PATH_PREFIX of EXCLUDED_PATH_PREFIXES) {
|
|
86
|
-
if (filename.indexOf(EXCLUDED_PATH_PREFIX) === 0) {
|
|
87
|
-
return true
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
102
|
return false
|
|
92
103
|
}
|
|
93
104
|
|
|
@@ -10,7 +10,7 @@ module.exports = function extractSensitiveRanges (evidence) {
|
|
|
10
10
|
pattern.lastIndex = 0
|
|
11
11
|
|
|
12
12
|
const regexResult = pattern.exec(evidence.value)
|
|
13
|
-
if (regexResult
|
|
13
|
+
if (regexResult?.length > 1) {
|
|
14
14
|
const start = regexResult.index + (regexResult[0].length - regexResult[1].length)
|
|
15
15
|
const end = start + regexResult[1].length
|
|
16
16
|
return [{ start, end }]
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const log = require('../../../../log')
|
|
5
5
|
const vulnerabilities = require('../../vulnerabilities')
|
|
6
|
+
const defaults = require('../../../../config/defaults')
|
|
6
7
|
|
|
7
8
|
const { contains, intersects, remove } = require('./range-utils')
|
|
8
9
|
|
|
@@ -14,14 +15,12 @@ const sqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyz
|
|
|
14
15
|
const taintedRangeBasedSensitiveAnalyzer = require('./sensitive-analyzers/tainted-range-based-sensitive-analyzer')
|
|
15
16
|
const urlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
|
|
16
17
|
|
|
17
|
-
const { DEFAULT_IAST_REDACTION_NAME_PATTERN, DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./sensitive-regex')
|
|
18
|
-
|
|
19
18
|
const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
20
19
|
|
|
21
20
|
class SensitiveHandler {
|
|
22
21
|
constructor () {
|
|
23
|
-
this._namePattern = new RegExp(
|
|
24
|
-
this._valuePattern = new RegExp(
|
|
22
|
+
this._namePattern = new RegExp(/** @type {string} */ (defaults['iast.redactionNamePattern']), 'gmi')
|
|
23
|
+
this._valuePattern = new RegExp(/** @type {string} */ (defaults['iast.redactionValuePattern']), 'gmi')
|
|
25
24
|
|
|
26
25
|
this._sensitiveAnalyzers = new Map()
|
|
27
26
|
this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|