dd-trace 5.89.0 → 5.90.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 -3
- package/index.d.ts +31 -0
- package/package.json +8 -8
- package/packages/datadog-instrumentations/src/azure-durable-functions.js +75 -0
- package/packages/datadog-instrumentations/src/cucumber.js +40 -1
- package/packages/datadog-instrumentations/src/elasticsearch.js +12 -3
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/jest.js +22 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +10 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +10 -2
- package/packages/datadog-instrumentations/src/playwright.js +20 -2
- package/packages/datadog-instrumentations/src/prisma.js +4 -2
- package/packages/datadog-instrumentations/src/vitest.js +16 -0
- package/packages/datadog-plugin-apollo/src/gateway/execute.js +8 -0
- package/packages/datadog-plugin-apollo/src/gateway/fetch.js +5 -0
- package/packages/datadog-plugin-apollo/src/gateway/plan.js +8 -0
- package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +5 -0
- package/packages/datadog-plugin-apollo/src/gateway/request.js +4 -3
- package/packages/datadog-plugin-apollo/src/gateway/validate.js +4 -3
- package/packages/datadog-plugin-apollo/src/index.js +28 -0
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +49 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +11 -1
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-playwright/src/index.js +35 -8
- package/packages/dd-trace/src/aiguard/noop.js +1 -1
- package/packages/dd-trace/src/aiguard/sdk.js +14 -5
- package/packages/dd-trace/src/appsec/api_security_sampler.js +22 -1
- package/packages/dd-trace/src/appsec/index.js +11 -1
- package/packages/dd-trace/src/appsec/reporter.js +28 -11
- package/packages/dd-trace/src/appsec/waf/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
- package/packages/dd-trace/src/config/index.js +1 -0
- package/packages/dd-trace/src/config/supported-configurations.json +7 -0
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +1 -8
- package/packages/dd-trace/src/encode/agentless-json.js +67 -22
- package/packages/dd-trace/src/exporters/agentless/index.js +58 -15
- package/packages/dd-trace/src/exporters/agentless/writer.js +35 -18
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic.js +9 -0
- package/packages/dd-trace/src/llmobs/tagger.js +8 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -0
- package/packages/dd-trace/src/plugins/apollo.js +7 -2
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +95 -3
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +36 -2
- package/packages/dd-trace/src/plugins/util/web.js +31 -11
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +4 -0
- package/packages/dd-trace/src/standalone/product.js +2 -1
|
@@ -5,6 +5,14 @@ const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
|
|
|
5
5
|
class ApolloGatewayPlanPlugin extends ApolloBasePlugin {
|
|
6
6
|
static operation = 'plan'
|
|
7
7
|
static prefix = 'tracing:apm:apollo:gateway:plan'
|
|
8
|
+
|
|
9
|
+
onEnd (ctx) {
|
|
10
|
+
const span = ctx?.currentStore?.span
|
|
11
|
+
|
|
12
|
+
if (!span) return
|
|
13
|
+
|
|
14
|
+
this.config.hooks.plan(span, ctx)
|
|
15
|
+
}
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
module.exports = ApolloGatewayPlanPlugin
|
|
@@ -5,6 +5,11 @@ const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
|
|
|
5
5
|
class ApolloGatewayPostProcessingPlugin extends ApolloBasePlugin {
|
|
6
6
|
static operation = 'postprocessing'
|
|
7
7
|
static prefix = 'tracing:apm:apollo:gateway:postprocessing'
|
|
8
|
+
|
|
9
|
+
onAsyncStart (ctx) {
|
|
10
|
+
const span = ctx?.currentStore?.span
|
|
11
|
+
this.config.hooks.postprocessing(span, ctx)
|
|
12
|
+
}
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
module.exports = ApolloGatewayPostProcessingPlugin
|
|
@@ -52,15 +52,16 @@ class ApolloGatewayRequestPlugin extends ApolloBasePlugin {
|
|
|
52
52
|
return ctx.currentStore
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
onAsyncStart (ctx) {
|
|
56
56
|
const errors = ctx?.result?.errors
|
|
57
57
|
// apollo gateway catches certain errors and returns them in the result object
|
|
58
58
|
// we want to capture these errors as spans
|
|
59
59
|
if (Array.isArray(errors) && errors.at(-1)?.stack && errors.at(-1).message) {
|
|
60
60
|
ctx.currentStore.span.setTag('error', errors.at(-1))
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
|
|
63
|
+
const span = ctx?.currentStore?.span
|
|
64
|
+
this.config.hooks.request(span, ctx)
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -6,16 +6,17 @@ class ApolloGatewayValidatePlugin extends ApolloBasePlugin {
|
|
|
6
6
|
static operation = 'validate'
|
|
7
7
|
static prefix = 'tracing:apm:apollo:gateway:validate'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
onEnd (ctx) {
|
|
10
10
|
const result = ctx.result
|
|
11
|
-
const span = ctx
|
|
11
|
+
const span = ctx?.currentStore?.span
|
|
12
12
|
|
|
13
13
|
if (!span) return
|
|
14
14
|
|
|
15
15
|
if (Array.isArray(result) && result.at(-1)?.stack && result.at(-1).message) {
|
|
16
16
|
span.setTag('error', result.at(-1))
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
this.config.hooks.validate(span, ctx)
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -10,6 +10,34 @@ class ApolloPlugin extends CompositePlugin {
|
|
|
10
10
|
gateway: ApolloGatewayPlugin,
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @override
|
|
16
|
+
*/
|
|
17
|
+
configure (config) {
|
|
18
|
+
return super.configure(validateConfig(config))
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const noop = () => {}
|
|
23
|
+
|
|
24
|
+
function validateConfig (config) {
|
|
25
|
+
return {
|
|
26
|
+
...config,
|
|
27
|
+
hooks: getHooks(config),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getHooks (config) {
|
|
32
|
+
const hooks = config?.hooks
|
|
33
|
+
const request = hooks?.request ?? noop
|
|
34
|
+
const validate = hooks?.validate ?? noop
|
|
35
|
+
const plan = hooks?.plan ?? noop
|
|
36
|
+
const execute = hooks?.execute ?? noop
|
|
37
|
+
const fetch = hooks?.fetch ?? noop
|
|
38
|
+
const postprocessing = hooks?.postprocessing ?? noop
|
|
39
|
+
|
|
40
|
+
return { request, validate, plan, execute, fetch, postprocessing }
|
|
13
41
|
}
|
|
14
42
|
|
|
15
43
|
module.exports = ApolloPlugin
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
|
|
5
|
+
class AzureDurableFunctionsPlugin extends TracingPlugin {
|
|
6
|
+
static get id () { return 'azure-durable-functions' }
|
|
7
|
+
static get operation () { return 'invoke' }
|
|
8
|
+
static get prefix () { return 'tracing:datadog:azure:durable-functions:invoke' }
|
|
9
|
+
static get type () { return 'serverless' }
|
|
10
|
+
static get kind () { return 'server' }
|
|
11
|
+
|
|
12
|
+
bindStart (ctx) {
|
|
13
|
+
const span = this.startSpan(this.operationName(), {
|
|
14
|
+
kind: 'internal',
|
|
15
|
+
type: 'serverless',
|
|
16
|
+
|
|
17
|
+
meta: {
|
|
18
|
+
component: 'azure-functions',
|
|
19
|
+
'aas.function.name': ctx.functionName,
|
|
20
|
+
'aas.function.trigger': ctx.trigger,
|
|
21
|
+
'resource.name': `${ctx.trigger} ${ctx.functionName}`,
|
|
22
|
+
},
|
|
23
|
+
}, ctx)
|
|
24
|
+
|
|
25
|
+
// in the case of entity functions, operationName should be available
|
|
26
|
+
if (ctx.operationName) {
|
|
27
|
+
span.setTag('aas.function.operation', ctx.operationName)
|
|
28
|
+
span.setTag('resource.name', `${ctx.trigger} ${ctx.functionName} ${ctx.operationName}`
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ctx.span = span
|
|
33
|
+
return ctx.currentStore
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
end (ctx) {
|
|
37
|
+
// We only want to run finish here if this is a synchronous operation
|
|
38
|
+
// Only synchronous operations would have `result` or `error` on `end`
|
|
39
|
+
// So we skip operations that dont
|
|
40
|
+
if (!ctx.hasOwnProperty('result') && !ctx.hasOwnProperty('error')) return
|
|
41
|
+
super.finish(ctx)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
asyncStart (ctx) {
|
|
45
|
+
super.finish(ctx)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = AzureDurableFunctionsPlugin
|
|
@@ -247,6 +247,7 @@ class CypressPlugin {
|
|
|
247
247
|
isSuitesSkippingEnabled = false
|
|
248
248
|
isCodeCoverageEnabled = false
|
|
249
249
|
isFlakyTestRetriesEnabled = false
|
|
250
|
+
flakyTestRetriesCount = 0
|
|
250
251
|
isEarlyFlakeDetectionEnabled = false
|
|
251
252
|
isKnownTestsEnabled = false
|
|
252
253
|
earlyFlakeDetectionNumRetries = 0
|
|
@@ -354,7 +355,10 @@ class CypressPlugin {
|
|
|
354
355
|
this.isKnownTestsEnabled = isKnownTestsEnabled
|
|
355
356
|
if (isFlakyTestRetriesEnabled && this.isTestIsolationEnabled) {
|
|
356
357
|
this.isFlakyTestRetriesEnabled = true
|
|
357
|
-
this.
|
|
358
|
+
this.flakyTestRetriesCount = flakyTestRetriesCount ?? 0
|
|
359
|
+
this.cypressConfig.retries.runMode = this.flakyTestRetriesCount
|
|
360
|
+
} else {
|
|
361
|
+
this.flakyTestRetriesCount = 0
|
|
358
362
|
}
|
|
359
363
|
this.isTestManagementTestsEnabled = isTestManagementEnabled
|
|
360
364
|
this.testManagementAttemptToFixRetries = testManagementAttemptToFixRetries
|
|
@@ -1019,6 +1023,12 @@ class CypressPlugin {
|
|
|
1019
1023
|
}
|
|
1020
1024
|
}
|
|
1021
1025
|
}
|
|
1026
|
+
// ATR: set TEST_HAS_FAILED_ALL_RETRIES when all auto test retries were exhausted and every attempt failed
|
|
1027
|
+
if (this.isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry &&
|
|
1028
|
+
this.flakyTestRetriesCount > 0 && testStatuses.length === this.flakyTestRetriesCount + 1 &&
|
|
1029
|
+
testStatuses.every(status => status === 'fail')) {
|
|
1030
|
+
this.activeTestSpan.setTag(TEST_HAS_FAILED_ALL_RETRIES, 'true')
|
|
1031
|
+
}
|
|
1022
1032
|
|
|
1023
1033
|
// Ensure quarantined tests reported from support.js are tagged
|
|
1024
1034
|
// (This catches cases where the test ran but failed, but Cypress saw it as passed)
|
|
@@ -386,6 +386,12 @@ class JestPlugin extends CiPlugin {
|
|
|
386
386
|
return ctx.currentStore
|
|
387
387
|
})
|
|
388
388
|
|
|
389
|
+
this.addBind('ci:jest:test-suite:hook:fn', (ctx) => {
|
|
390
|
+
const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(ctx.testSuiteAbsolutePath)
|
|
391
|
+
const store = storage('legacy').getStore()
|
|
392
|
+
return { ...store, span: testSuiteSpan }
|
|
393
|
+
})
|
|
394
|
+
|
|
389
395
|
this.addSub('ci:jest:test:finish', ({
|
|
390
396
|
span,
|
|
391
397
|
status,
|
|
@@ -114,11 +114,14 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
this.addBind('ci:playwright:test-suite:start', (ctx) => {
|
|
117
|
-
const { testSuiteAbsolutePath } = ctx
|
|
117
|
+
const { testSuiteAbsolutePath, testSourceFileAbsolutePath } = ctx
|
|
118
118
|
|
|
119
119
|
const store = storage('legacy').getStore()
|
|
120
120
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
121
|
-
const testSourceFile = getTestSuitePath(
|
|
121
|
+
const testSourceFile = getTestSuitePath(
|
|
122
|
+
testSourceFileAbsolutePath || testSuiteAbsolutePath,
|
|
123
|
+
this.repositoryRoot
|
|
124
|
+
)
|
|
122
125
|
|
|
123
126
|
const testSuiteMetadata = {
|
|
124
127
|
...getTestSuiteCommonTags(
|
|
@@ -238,12 +241,15 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
238
241
|
// test_suite_absolute_path is just a hack because in the worker we don't have rootDir and repositoryRoot
|
|
239
242
|
// but if we pass those the same way we pass `DD_PLAYWRIGHT_WORKER` this is not necessary
|
|
240
243
|
const testSuitePath = getTestSuitePath(formattedSpan.meta.test_suite_absolute_path, this.rootDir)
|
|
241
|
-
const
|
|
244
|
+
const testSourceAbsolutePath = formattedSpan.meta.test_source_absolute_path ||
|
|
245
|
+
formattedSpan.meta.test_suite_absolute_path
|
|
246
|
+
const testSourceFile = getTestSuitePath(testSourceAbsolutePath, this.repositoryRoot)
|
|
242
247
|
// we need to rewrite this because this.rootDir and this.repositoryRoot are not available in the worker
|
|
243
248
|
formattedSpan.meta[TEST_SUITE] = testSuitePath
|
|
244
249
|
formattedSpan.meta[TEST_SOURCE_FILE] = testSourceFile
|
|
245
250
|
formattedSpan.resource = `${testSuitePath}.${formattedSpan.meta[TEST_NAME]}`
|
|
246
251
|
delete formattedSpan.meta.test_suite_absolute_path
|
|
252
|
+
delete formattedSpan.meta.test_source_absolute_path
|
|
247
253
|
}
|
|
248
254
|
formattedTrace.push(formattedSpan)
|
|
249
255
|
}
|
|
@@ -259,20 +265,25 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
259
265
|
const {
|
|
260
266
|
testName,
|
|
261
267
|
testSuiteAbsolutePath,
|
|
268
|
+
testSourceFileAbsolutePath,
|
|
262
269
|
testSourceLine,
|
|
263
270
|
browserName,
|
|
264
271
|
isDisabled,
|
|
265
272
|
} = ctx
|
|
266
273
|
const store = storage('legacy').getStore()
|
|
267
274
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
268
|
-
const testSourceFile = getTestSuitePath(
|
|
275
|
+
const testSourceFile = getTestSuitePath(
|
|
276
|
+
testSourceFileAbsolutePath || testSuiteAbsolutePath,
|
|
277
|
+
this.repositoryRoot
|
|
278
|
+
)
|
|
269
279
|
const span = this.startTestSpan(
|
|
270
280
|
testName,
|
|
271
281
|
testSuiteAbsolutePath,
|
|
272
282
|
testSuite,
|
|
273
283
|
testSourceFile,
|
|
274
284
|
testSourceLine,
|
|
275
|
-
browserName
|
|
285
|
+
browserName,
|
|
286
|
+
testSourceFileAbsolutePath
|
|
276
287
|
)
|
|
277
288
|
|
|
278
289
|
if (isDisabled) {
|
|
@@ -408,6 +419,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
408
419
|
this.addSub('ci:playwright:test:skip', ({
|
|
409
420
|
testName,
|
|
410
421
|
testSuiteAbsolutePath,
|
|
422
|
+
testSourceFileAbsolutePath,
|
|
411
423
|
testSourceLine,
|
|
412
424
|
browserName,
|
|
413
425
|
isNew,
|
|
@@ -416,14 +428,18 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
416
428
|
isQuarantined,
|
|
417
429
|
}) => {
|
|
418
430
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
419
|
-
const testSourceFile = getTestSuitePath(
|
|
431
|
+
const testSourceFile = getTestSuitePath(
|
|
432
|
+
testSourceFileAbsolutePath || testSuiteAbsolutePath,
|
|
433
|
+
this.repositoryRoot
|
|
434
|
+
)
|
|
420
435
|
const span = this.startTestSpan(
|
|
421
436
|
testName,
|
|
422
437
|
testSuiteAbsolutePath,
|
|
423
438
|
testSuite,
|
|
424
439
|
testSourceFile,
|
|
425
440
|
testSourceLine,
|
|
426
|
-
browserName
|
|
441
|
+
browserName,
|
|
442
|
+
testSourceFileAbsolutePath
|
|
427
443
|
)
|
|
428
444
|
|
|
429
445
|
span.setTag(TEST_STATUS, 'skip')
|
|
@@ -446,7 +462,15 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
446
462
|
}
|
|
447
463
|
|
|
448
464
|
// TODO: this runs both in worker and main process (main process: skipped tests that do not go through _runTest)
|
|
449
|
-
startTestSpan (
|
|
465
|
+
startTestSpan (
|
|
466
|
+
testName,
|
|
467
|
+
testSuiteAbsolutePath,
|
|
468
|
+
testSuite,
|
|
469
|
+
testSourceFile,
|
|
470
|
+
testSourceLine,
|
|
471
|
+
browserName,
|
|
472
|
+
testSourceFileAbsolutePath
|
|
473
|
+
) {
|
|
450
474
|
const testSuiteSpan = this._testSuiteSpansByTestSuiteAbsolutePath.get(testSuiteAbsolutePath)
|
|
451
475
|
|
|
452
476
|
const extraTags = {
|
|
@@ -462,6 +486,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
462
486
|
}
|
|
463
487
|
|
|
464
488
|
extraTags.test_suite_absolute_path = testSuiteAbsolutePath
|
|
489
|
+
if (testSourceFileAbsolutePath) {
|
|
490
|
+
extraTags.test_source_absolute_path = testSourceFileAbsolutePath
|
|
491
|
+
}
|
|
465
492
|
|
|
466
493
|
return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
|
|
467
494
|
}
|
|
@@ -4,6 +4,8 @@ const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: fa
|
|
|
4
4
|
const log = require('../log')
|
|
5
5
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
6
6
|
const tracerVersion = require('../../../../package.json').version
|
|
7
|
+
const { keepTrace } = require('../priority_sampler')
|
|
8
|
+
const { AI_GUARD } = require('../standalone/product')
|
|
7
9
|
const NoopAIGuard = require('./noop')
|
|
8
10
|
const executeRequest = require('./client')
|
|
9
11
|
const {
|
|
@@ -23,11 +25,12 @@ const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
|
|
|
23
25
|
const ALLOW = 'ALLOW'
|
|
24
26
|
|
|
25
27
|
class AIGuardAbortError extends Error {
|
|
26
|
-
constructor (reason, tags) {
|
|
28
|
+
constructor (reason, tags, sds) {
|
|
27
29
|
super(reason)
|
|
28
30
|
this.name = 'AIGuardAbortError'
|
|
29
31
|
this.reason = reason
|
|
30
32
|
this.tags = tags
|
|
33
|
+
this.sds = sds || []
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -135,7 +138,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
135
138
|
if (!this.#initialized) {
|
|
136
139
|
return super.evaluate(messages, opts)
|
|
137
140
|
}
|
|
138
|
-
const { block =
|
|
141
|
+
const { block = true } = opts ?? {}
|
|
139
142
|
return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (span) => {
|
|
140
143
|
const last = messages[messages.length - 1]
|
|
141
144
|
const target = this.#isToolCall(last) ? 'tool' : 'prompt'
|
|
@@ -152,6 +155,12 @@ class AIGuard extends NoopAIGuard {
|
|
|
152
155
|
span.meta_struct = {
|
|
153
156
|
[AI_GUARD_META_STRUCT_KEY]: metaStruct,
|
|
154
157
|
}
|
|
158
|
+
const rootSpan = span.context()?._trace?.started?.[0]
|
|
159
|
+
if (rootSpan) {
|
|
160
|
+
// keepTrace must be called before executeRequest so the sampling decision
|
|
161
|
+
// is propagated correctly to outgoing HTTP client calls.
|
|
162
|
+
keepTrace(rootSpan, AI_GUARD)
|
|
163
|
+
}
|
|
155
164
|
let response
|
|
156
165
|
try {
|
|
157
166
|
const payload = {
|
|
@@ -184,7 +193,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
184
193
|
action = attr.action
|
|
185
194
|
reason = attr.reason
|
|
186
195
|
tags = attr.tags
|
|
187
|
-
sdsFindings = attr.sds_findings
|
|
196
|
+
sdsFindings = attr.sds_findings || []
|
|
188
197
|
blockingEnabled = attr.is_blocking_enabled ?? false
|
|
189
198
|
} catch (e) {
|
|
190
199
|
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
|
|
@@ -204,9 +213,9 @@ class AIGuard extends NoopAIGuard {
|
|
|
204
213
|
}
|
|
205
214
|
if (shouldBlock) {
|
|
206
215
|
span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
|
|
207
|
-
throw new AIGuardAbortError(reason, tags)
|
|
216
|
+
throw new AIGuardAbortError(reason, tags, sdsFindings)
|
|
208
217
|
}
|
|
209
|
-
return { action, reason, tags }
|
|
218
|
+
return { action, reason, tags, sds: sdsFindings }
|
|
210
219
|
})
|
|
211
220
|
}
|
|
212
221
|
}
|
|
@@ -70,8 +70,25 @@ function isSampled (key) {
|
|
|
70
70
|
return sampledRequests.has(key)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function getRouteOrEndpoint (context, statusCode) {
|
|
74
|
+
// First try to get the route from the context paths
|
|
75
|
+
const route = context?.paths?.join('') || ''
|
|
76
|
+
if (route) {
|
|
77
|
+
return route
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If route is not available, fallback to http.endpoint
|
|
81
|
+
if (statusCode !== 404) {
|
|
82
|
+
const endpoint = context?.span?.context()?._tags?.['http.endpoint']
|
|
83
|
+
if (endpoint) {
|
|
84
|
+
return endpoint
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return ''
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
function computeKey (req, res) {
|
|
74
|
-
const route = web.getContext(req)?.paths?.join('') || ''
|
|
75
92
|
const method = req.method
|
|
76
93
|
const status = res.statusCode
|
|
77
94
|
|
|
@@ -79,6 +96,10 @@ function computeKey (req, res) {
|
|
|
79
96
|
log.warn('[ASM] Unsupported groupkey for API security')
|
|
80
97
|
return null
|
|
81
98
|
}
|
|
99
|
+
|
|
100
|
+
const context = web.getContext(req)
|
|
101
|
+
const route = getRouteOrEndpoint(context, status)
|
|
102
|
+
|
|
82
103
|
return method + route + status
|
|
83
104
|
}
|
|
84
105
|
|
|
@@ -68,7 +68,7 @@ function enable (_config) {
|
|
|
68
68
|
|
|
69
69
|
appsecRemoteConfig.enableWafUpdate(_config.appsec)
|
|
70
70
|
|
|
71
|
-
Reporter.init(_config.appsec)
|
|
71
|
+
Reporter.init(_config.appsec, _config.inferredProxyServicesEnabled)
|
|
72
72
|
|
|
73
73
|
apiSecuritySampler.configure(_config)
|
|
74
74
|
|
|
@@ -174,6 +174,13 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
|
|
|
174
174
|
[HTTP_CLIENT_IP]: clientIp,
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
+
if (config.inferredProxyServicesEnabled) {
|
|
178
|
+
const context = web.getContext(req)
|
|
179
|
+
if (context?.inferredProxySpan) {
|
|
180
|
+
context.inferredProxySpan.setTag('_dd.appsec.enabled', 1)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
177
184
|
const requestHeaders = { ...req.headers }
|
|
178
185
|
delete requestHeaders.cookie
|
|
179
186
|
|
|
@@ -226,6 +233,9 @@ function incomingHttpEndTranslator ({ req, res }) {
|
|
|
226
233
|
persistent[addresses.HTTP_INCOMING_QUERY] = query
|
|
227
234
|
}
|
|
228
235
|
|
|
236
|
+
// This hook runs before span finish, so ensure route/endpoint tags are available before API Security sampling runs.
|
|
237
|
+
web.setRouteOrEndpointTag(req)
|
|
238
|
+
|
|
229
239
|
if (apiSecuritySampler.sampleRequest(req, res, true)) {
|
|
230
240
|
persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
|
|
231
241
|
}
|
|
@@ -34,6 +34,7 @@ const config = {
|
|
|
34
34
|
maxHeadersCollected: 0,
|
|
35
35
|
headersRedaction: false,
|
|
36
36
|
raspBodyCollection: false,
|
|
37
|
+
inferredProxyServicesEnabled: false,
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
const metricsQueue = new Map()
|
|
@@ -103,11 +104,12 @@ const NON_EXTENDED_REQUEST_HEADERS = new Set([...requestHeadersList, ...eventHea
|
|
|
103
104
|
const NON_EXTENDED_RESPONSE_HEADERS = new Set(responseHeaderList)
|
|
104
105
|
const REDACTED_HEADERS = new Set(redactedHeadersList)
|
|
105
106
|
|
|
106
|
-
function init (_config) {
|
|
107
|
+
function init (_config, inferredProxyServicesEnabled) {
|
|
107
108
|
config.headersExtendedCollectionEnabled = _config.extendedHeadersCollection.enabled
|
|
108
109
|
config.maxHeadersCollected = _config.extendedHeadersCollection.maxHeaders
|
|
109
110
|
config.headersRedaction = _config.extendedHeadersCollection.redaction
|
|
110
111
|
config.raspBodyCollection = _config.rasp.bodyCollection
|
|
112
|
+
config.inferredProxyServicesEnabled = inferredProxyServicesEnabled
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
function formatHeaderName (name) {
|
|
@@ -298,9 +300,11 @@ function reportWafConfigUpdate (product, rcConfigId, diagnostics, wafVersion) {
|
|
|
298
300
|
}
|
|
299
301
|
}
|
|
300
302
|
|
|
301
|
-
function reportMetrics (metrics, raspRule) {
|
|
302
|
-
|
|
303
|
-
|
|
303
|
+
function reportMetrics (metrics, raspRule, req) {
|
|
304
|
+
if (!req) {
|
|
305
|
+
req = storage('legacy').getStore()?.req
|
|
306
|
+
}
|
|
307
|
+
const rootSpan = req && web.root(req)
|
|
304
308
|
|
|
305
309
|
if (!rootSpan) return
|
|
306
310
|
|
|
@@ -309,9 +313,9 @@ function reportMetrics (metrics, raspRule) {
|
|
|
309
313
|
}
|
|
310
314
|
|
|
311
315
|
if (raspRule) {
|
|
312
|
-
updateRaspRequestsMetricTags(metrics,
|
|
316
|
+
updateRaspRequestsMetricTags(metrics, req, raspRule)
|
|
313
317
|
} else {
|
|
314
|
-
updateWafRequestsMetricTags(metrics,
|
|
318
|
+
updateWafRequestsMetricTags(metrics, req)
|
|
315
319
|
}
|
|
316
320
|
|
|
317
321
|
reportTruncationMetrics(rootSpan, metrics)
|
|
@@ -331,9 +335,11 @@ function reportTruncationMetrics (rootSpan, metrics) {
|
|
|
331
335
|
}
|
|
332
336
|
}
|
|
333
337
|
|
|
334
|
-
function reportAttack ({ events: attackData, actions }) {
|
|
335
|
-
|
|
336
|
-
|
|
338
|
+
function reportAttack ({ events: attackData, actions }, req) {
|
|
339
|
+
if (!req) {
|
|
340
|
+
req = storage('legacy').getStore()?.req
|
|
341
|
+
}
|
|
342
|
+
|
|
337
343
|
const rootSpan = web.root(req)
|
|
338
344
|
if (!rootSpan) return
|
|
339
345
|
|
|
@@ -362,6 +368,14 @@ function reportAttack ({ events: attackData, actions }) {
|
|
|
362
368
|
|
|
363
369
|
rootSpan.addTags(newTags)
|
|
364
370
|
|
|
371
|
+
// Add _dd.appsec.json tag to inferred proxy span
|
|
372
|
+
if (config.inferredProxyServicesEnabled) {
|
|
373
|
+
const context = web.getContext(req)
|
|
374
|
+
if (context?.inferredProxySpan) {
|
|
375
|
+
context.inferredProxySpan.setTag('_dd.appsec.json', newTags['_dd.appsec.json'])
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
365
379
|
// TODO this should be deleted in a major
|
|
366
380
|
if (config.raspBodyCollection && isRaspAttack(attackData)) {
|
|
367
381
|
reportRequestBody(rootSpan, req.body, true)
|
|
@@ -463,10 +477,13 @@ function isSchemaAttribute (attribute) {
|
|
|
463
477
|
return attribute.startsWith('_dd.appsec.s.')
|
|
464
478
|
}
|
|
465
479
|
|
|
466
|
-
function reportAttributes (attributes) {
|
|
480
|
+
function reportAttributes (attributes, req) {
|
|
467
481
|
if (!attributes) return
|
|
468
482
|
|
|
469
|
-
|
|
483
|
+
if (!req) {
|
|
484
|
+
req = storage('legacy').getStore()?.req
|
|
485
|
+
}
|
|
486
|
+
|
|
470
487
|
const rootSpan = web.root(req)
|
|
471
488
|
|
|
472
489
|
if (!rootSpan) return
|
|
@@ -122,7 +122,7 @@ function run (data, req, raspRule) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
const wafContext = waf.wafManager.getWAFContext(req)
|
|
125
|
-
const result = wafContext.run(data, raspRule)
|
|
125
|
+
const result = wafContext.run(data, raspRule, req)
|
|
126
126
|
|
|
127
127
|
if (result?.keep) {
|
|
128
128
|
if (limiter.isAllowed()) {
|
|
@@ -22,7 +22,7 @@ class WAFContextWrapper {
|
|
|
22
22
|
this.cachedUserIdResults = new Map()
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
run ({ persistent, ephemeral }, raspRule) {
|
|
25
|
+
run ({ persistent, ephemeral }, raspRule, req) {
|
|
26
26
|
if (this.ddwafContext.disposed) {
|
|
27
27
|
log.warn('[ASM] Calling run on a disposed context')
|
|
28
28
|
if (raspRule) {
|
|
@@ -141,10 +141,10 @@ class WAFContextWrapper {
|
|
|
141
141
|
metrics.wafTimeout = result.timeout
|
|
142
142
|
|
|
143
143
|
if (ruleTriggered) {
|
|
144
|
-
Reporter.reportAttack(result)
|
|
144
|
+
Reporter.reportAttack(result, req)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
Reporter.reportAttributes(result.attributes)
|
|
147
|
+
Reporter.reportAttributes(result.attributes, req)
|
|
148
148
|
|
|
149
149
|
return result
|
|
150
150
|
} catch (err) {
|
|
@@ -156,7 +156,7 @@ class WAFContextWrapper {
|
|
|
156
156
|
wafRunFinished.publish({ payload })
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
Reporter.reportMetrics(metrics, raspRule)
|
|
159
|
+
Reporter.reportMetrics(metrics, raspRule, req)
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -1661,6 +1661,7 @@ function getAgentUrl (url, options) {
|
|
|
1661
1661
|
!options.port &&
|
|
1662
1662
|
!getEnv('DD_AGENT_HOST') &&
|
|
1663
1663
|
!getEnv('DD_TRACE_AGENT_PORT') &&
|
|
1664
|
+
!isTrue(getEnv('DD_CIVISIBILITY_AGENTLESS_ENABLED')) &&
|
|
1664
1665
|
fs.existsSync('/var/run/datadog/apm.socket')
|
|
1665
1666
|
) {
|
|
1666
1667
|
return new URL('unix:///var/run/datadog/apm.socket')
|
|
@@ -2182,6 +2182,13 @@
|
|
|
2182
2182
|
"default": "true"
|
|
2183
2183
|
}
|
|
2184
2184
|
],
|
|
2185
|
+
"DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED": [
|
|
2186
|
+
{
|
|
2187
|
+
"implementation": "B",
|
|
2188
|
+
"type": "boolean",
|
|
2189
|
+
"default": "true"
|
|
2190
|
+
}
|
|
2191
|
+
],
|
|
2185
2192
|
"DD_TRACE_AZURE_EVENTHUBS_BATCH_LINKS_ENABLED": [
|
|
2186
2193
|
{
|
|
2187
2194
|
"implementation": "A",
|
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
SAMPLING_MECHANISM_SPAN: 8,
|
|
18
18
|
SAMPLING_MECHANISM_REMOTE_USER: 11,
|
|
19
19
|
SAMPLING_MECHANISM_REMOTE_DYNAMIC: 12,
|
|
20
|
+
SAMPLING_MECHANISM_AI_GUARD: 13,
|
|
20
21
|
SPAN_SAMPLING_MECHANISM: '_dd.span_sampling.mechanism',
|
|
21
22
|
SPAN_SAMPLING_RULE_RATE: '_dd.span_sampling.rule_rate',
|
|
22
23
|
SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
|
|
@@ -265,14 +265,7 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
265
265
|
}
|
|
266
266
|
const startTime = Date.now()
|
|
267
267
|
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
const testSessionEvents = rawEvents.filter(
|
|
271
|
-
event => event.type === 'test_session_end' || event.type === 'test_suite_end' || event.type === 'test_module_end'
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
const isTestSessionTrace = !!testSessionEvents.length
|
|
275
|
-
const events = isTestSessionTrace ? testSessionEvents : rawEvents
|
|
268
|
+
const events = trace.map(formatSpan)
|
|
276
269
|
|
|
277
270
|
this._eventCount += events.length
|
|
278
271
|
|