dd-trace 6.0.0-pre-71c4307 → 6.0.0-pre-0d49ecf
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/package.json +3 -3
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/jest.js +11 -3
- package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
- package/packages/datadog-instrumentations/src/openai.js +2 -0
- package/packages/datadog-instrumentations/src/vitest.js +25 -12
- package/packages/datadog-instrumentations/src/vm.js +49 -0
- package/packages/datadog-plugin-http/src/client.js +5 -33
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-langchain/src/index.js +12 -80
- package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +10 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
- package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
- package/packages/dd-trace/src/crashtracking/noop.js +3 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -3
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +1 -6
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +9 -1
- package/packages/dd-trace/src/llmobs/util.js +7 -1
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
- package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +7 -2
- package/packages/dd-trace/src/plugins/util/test.js +6 -6
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
- package/packages/dd-trace/src/profiling/profiler.js +11 -8
- package/packages/dd-trace/src/proxy.js +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "6.0.0-pre-
|
|
3
|
+
"version": "6.0.0-pre-0d49ecf",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"test:lambda:ci": "nyc --no-clean --include \"packages/dd-trace/src/lambda/**/*.js\" -- npm run test:lambda",
|
|
34
34
|
"test:llmobs:sdk": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/llmobs/plugins/**/*.spec.js\" \"packages/dd-trace/test/llmobs/**/*.spec.js\" ",
|
|
35
35
|
"test:llmobs:sdk:ci": "nyc --no-clean --include \"packages/dd-trace/src/llmobs/**/*.js\" -- npm run test:llmobs:sdk",
|
|
36
|
-
"test:llmobs:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/llmobs/plugins
|
|
36
|
+
"test:llmobs:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/llmobs/plugins/@($(echo $PLUGINS))/*.spec.js\"",
|
|
37
37
|
"test:llmobs:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/src/llmobs/**/*.js\" -- npm run test:llmobs:plugins",
|
|
38
38
|
"test:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\"",
|
|
39
39
|
"test:plugins:ci": "yarn services && nyc --no-clean --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS)).js\" --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS))/**/*.js\" --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" -- npm run test:plugins",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"node": ">=18"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
|
-
"@datadog/libdatadog": "^0.
|
|
84
|
+
"@datadog/libdatadog": "^0.4.0",
|
|
85
85
|
"@datadog/native-appsec": "8.4.0",
|
|
86
86
|
"@datadog/native-iast-rewriter": "2.6.1",
|
|
87
87
|
"@datadog/native-iast-taint-tracking": "3.2.0",
|
|
@@ -88,6 +88,7 @@ module.exports = {
|
|
|
88
88
|
mysql2: () => require('../mysql2'),
|
|
89
89
|
net: () => require('../net'),
|
|
90
90
|
next: () => require('../next'),
|
|
91
|
+
'node-serialize': () => require('../node-serialize'),
|
|
91
92
|
'node:child_process': () => require('../child_process'),
|
|
92
93
|
'node:crypto': () => require('../crypto'),
|
|
93
94
|
'node:dns': () => require('../dns'),
|
|
@@ -96,6 +97,7 @@ module.exports = {
|
|
|
96
97
|
'node:https': () => require('../http'),
|
|
97
98
|
'node:net': () => require('../net'),
|
|
98
99
|
'node:url': () => require('../url'),
|
|
100
|
+
'node:vm': () => require('../vm'),
|
|
99
101
|
nyc: () => require('../nyc'),
|
|
100
102
|
oracledb: () => require('../oracledb'),
|
|
101
103
|
openai: () => require('../openai'),
|
|
@@ -122,6 +124,7 @@ module.exports = {
|
|
|
122
124
|
undici: () => require('../undici'),
|
|
123
125
|
url: () => require('../url'),
|
|
124
126
|
vitest: { esmFirst: true, fn: () => require('../vitest') },
|
|
127
|
+
vm: () => require('../vm'),
|
|
125
128
|
when: () => require('../when'),
|
|
126
129
|
winston: () => require('../winston'),
|
|
127
130
|
workerpool: () => require('../mocha')
|
|
@@ -12,7 +12,8 @@ const {
|
|
|
12
12
|
getTestParametersString,
|
|
13
13
|
addEfdStringToTestName,
|
|
14
14
|
removeEfdStringFromTestName,
|
|
15
|
-
getIsFaultyEarlyFlakeDetection
|
|
15
|
+
getIsFaultyEarlyFlakeDetection,
|
|
16
|
+
JEST_WORKER_LOGS_PAYLOAD_CODE
|
|
16
17
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
17
18
|
const {
|
|
18
19
|
getFormattedJestTestParameters,
|
|
@@ -30,6 +31,7 @@ const testSuiteFinishCh = channel('ci:jest:test-suite:finish')
|
|
|
30
31
|
|
|
31
32
|
const workerReportTraceCh = channel('ci:jest:worker-report:trace')
|
|
32
33
|
const workerReportCoverageCh = channel('ci:jest:worker-report:coverage')
|
|
34
|
+
const workerReportLogsCh = channel('ci:jest:worker-report:logs')
|
|
33
35
|
|
|
34
36
|
const testSuiteCodeCoverageCh = channel('ci:jest:test-suite:code-coverage')
|
|
35
37
|
|
|
@@ -301,7 +303,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
301
303
|
const numRetries = this.global[RETRY_TIMES]
|
|
302
304
|
const numTestExecutions = event.test?.invocations
|
|
303
305
|
const willBeRetried = numRetries > 0 && numTestExecutions - 1 < numRetries
|
|
304
|
-
const mightHitBreakpoint = this.isDiEnabled && numTestExecutions >=
|
|
306
|
+
const mightHitBreakpoint = this.isDiEnabled && numTestExecutions >= 2
|
|
305
307
|
|
|
306
308
|
const asyncResource = asyncResources.get(event.test)
|
|
307
309
|
|
|
@@ -317,7 +319,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
317
319
|
|
|
318
320
|
// After finishing it might take a bit for the snapshot to be handled.
|
|
319
321
|
// This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
|
|
320
|
-
if (mightHitBreakpoint) {
|
|
322
|
+
if (status === 'fail' && mightHitBreakpoint) {
|
|
321
323
|
await new Promise(resolve => {
|
|
322
324
|
setTimeout(() => {
|
|
323
325
|
resolve()
|
|
@@ -979,6 +981,12 @@ addHook({
|
|
|
979
981
|
})
|
|
980
982
|
return
|
|
981
983
|
}
|
|
984
|
+
if (code === JEST_WORKER_LOGS_PAYLOAD_CODE) { // datadog logs payload
|
|
985
|
+
sessionAsyncResource.runInAsyncScope(() => {
|
|
986
|
+
workerReportLogsCh.publish(data)
|
|
987
|
+
})
|
|
988
|
+
return
|
|
989
|
+
}
|
|
982
990
|
return _onMessage.apply(this, arguments)
|
|
983
991
|
})
|
|
984
992
|
return childProcessWorker
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const shimmer = require('../../datadog-shimmer')
|
|
4
|
+
const { channel, addHook } = require('./helpers/instrument')
|
|
5
|
+
|
|
6
|
+
const nodeUnserializeCh = channel('datadog:node-serialize:unserialize:start')
|
|
7
|
+
|
|
8
|
+
function wrapUnserialize (serialize) {
|
|
9
|
+
return function wrappedUnserialize (obj) {
|
|
10
|
+
if (nodeUnserializeCh.hasSubscribers) {
|
|
11
|
+
nodeUnserializeCh.publish({ obj })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return serialize.apply(this, arguments)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
addHook({ name: 'node-serialize', versions: ['0.0.4'] }, serialize => {
|
|
19
|
+
shimmer.wrap(serialize, 'unserialize', wrapUnserialize)
|
|
20
|
+
|
|
21
|
+
return serialize
|
|
22
|
+
})
|
|
@@ -179,7 +179,9 @@ function getSortWrapper (sort) {
|
|
|
179
179
|
const knownTestsResponse = await getChannelPromise(knownTestsCh)
|
|
180
180
|
if (!knownTestsResponse.err) {
|
|
181
181
|
knownTests = knownTestsResponse.knownTests
|
|
182
|
-
const
|
|
182
|
+
const getFilePaths = this.ctx.getTestFilepaths || this.ctx._globTestFilepaths
|
|
183
|
+
|
|
184
|
+
const testFilepaths = await getFilePaths.call(this.ctx)
|
|
183
185
|
|
|
184
186
|
isEarlyFlakeDetectionFaultyCh.publish({
|
|
185
187
|
knownTests: knownTests.vitest || {},
|
|
@@ -492,15 +494,6 @@ addHook({
|
|
|
492
494
|
return vitestPackage
|
|
493
495
|
})
|
|
494
496
|
|
|
495
|
-
addHook({
|
|
496
|
-
name: 'vitest',
|
|
497
|
-
versions: ['>=2.1.0'],
|
|
498
|
-
filePattern: 'dist/chunks/RandomSequencer.*'
|
|
499
|
-
}, (randomSequencerPackage) => {
|
|
500
|
-
shimmer.wrap(randomSequencerPackage.B.prototype, 'sort', getSortWrapper)
|
|
501
|
-
return randomSequencerPackage
|
|
502
|
-
})
|
|
503
|
-
|
|
504
497
|
addHook({
|
|
505
498
|
name: 'vitest',
|
|
506
499
|
versions: ['>=2.0.5 <2.1.0'],
|
|
@@ -513,6 +506,24 @@ addHook({
|
|
|
513
506
|
return vitestPackage
|
|
514
507
|
})
|
|
515
508
|
|
|
509
|
+
addHook({
|
|
510
|
+
name: 'vitest',
|
|
511
|
+
versions: ['>=2.1.0 <3.0.0'],
|
|
512
|
+
filePattern: 'dist/chunks/RandomSequencer.*'
|
|
513
|
+
}, (randomSequencerPackage) => {
|
|
514
|
+
shimmer.wrap(randomSequencerPackage.B.prototype, 'sort', getSortWrapper)
|
|
515
|
+
return randomSequencerPackage
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
addHook({
|
|
519
|
+
name: 'vitest',
|
|
520
|
+
versions: ['>=3.0.0'],
|
|
521
|
+
filePattern: 'dist/chunks/resolveConfig.*'
|
|
522
|
+
}, (randomSequencerPackage) => {
|
|
523
|
+
shimmer.wrap(randomSequencerPackage.B.prototype, 'sort', getSortWrapper)
|
|
524
|
+
return randomSequencerPackage
|
|
525
|
+
})
|
|
526
|
+
|
|
516
527
|
// Can't specify file because compiled vitest includes hashes in their files
|
|
517
528
|
addHook({
|
|
518
529
|
name: 'vitest',
|
|
@@ -533,15 +544,17 @@ addHook({
|
|
|
533
544
|
versions: ['>=1.6.0'],
|
|
534
545
|
file: 'dist/index.js'
|
|
535
546
|
}, (vitestPackage, frameworkVersion) => {
|
|
536
|
-
shimmer.wrap(vitestPackage, 'startTests', startTests => async function (
|
|
547
|
+
shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPaths) {
|
|
537
548
|
let testSuiteError = null
|
|
538
549
|
if (!testSuiteStartCh.hasSubscribers) {
|
|
539
550
|
return startTests.apply(this, arguments)
|
|
540
551
|
}
|
|
552
|
+
// From >=3.0.1, the first arguments changes from a string to an object containing the filepath
|
|
553
|
+
const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
|
|
541
554
|
|
|
542
555
|
const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
543
556
|
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
544
|
-
testSuiteStartCh.publish({ testSuiteAbsolutePath
|
|
557
|
+
testSuiteStartCh.publish({ testSuiteAbsolutePath, frameworkVersion })
|
|
545
558
|
})
|
|
546
559
|
const startTestsResponse = await startTests.apply(this, arguments)
|
|
547
560
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { channel, addHook } = require('./helpers/instrument')
|
|
4
|
+
const shimmer = require('../../datadog-shimmer')
|
|
5
|
+
const names = ['vm', 'node:vm']
|
|
6
|
+
|
|
7
|
+
const runScriptStartChannel = channel('datadog:vm:run-script:start')
|
|
8
|
+
const sourceTextModuleStartChannel = channel('datadog:vm:source-text-module:start')
|
|
9
|
+
|
|
10
|
+
addHook({ name: names }, function (vm) {
|
|
11
|
+
vm.Script = class extends vm.Script {
|
|
12
|
+
constructor (code) {
|
|
13
|
+
super(...arguments)
|
|
14
|
+
|
|
15
|
+
if (runScriptStartChannel.hasSubscribers && code) {
|
|
16
|
+
runScriptStartChannel.publish({ code })
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (vm.SourceTextModule && typeof vm.SourceTextModule === 'function') {
|
|
22
|
+
vm.SourceTextModule = class extends vm.SourceTextModule {
|
|
23
|
+
constructor (code) {
|
|
24
|
+
super(...arguments)
|
|
25
|
+
|
|
26
|
+
if (sourceTextModuleStartChannel.hasSubscribers && code) {
|
|
27
|
+
sourceTextModuleStartChannel.publish({ code })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
shimmer.wrap(vm, 'runInContext', wrapVMMethod)
|
|
34
|
+
shimmer.wrap(vm, 'runInNewContext', wrapVMMethod)
|
|
35
|
+
shimmer.wrap(vm, 'runInThisContext', wrapVMMethod)
|
|
36
|
+
shimmer.wrap(vm, 'compileFunction', wrapVMMethod)
|
|
37
|
+
|
|
38
|
+
return vm
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function wrapVMMethod (original) {
|
|
42
|
+
return function wrappedVMMethod (code) {
|
|
43
|
+
if (runScriptStartChannel.hasSubscribers && code) {
|
|
44
|
+
runScriptStartChannel.publish({ code })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return original.apply(this, arguments)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -59,6 +59,11 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (this.shouldInjectTraceHeaders(options, uri)) {
|
|
62
|
+
// Clone the headers object in case an upstream lib has a reference to the original headers
|
|
63
|
+
// Implemented due to aws-sdk issue where request signing is broken if we mutate the headers
|
|
64
|
+
// Explained further in:
|
|
65
|
+
// https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1609#issuecomment-1826167348
|
|
66
|
+
options.headers = Object.assign({}, options.headers)
|
|
62
67
|
this.tracer.inject(span, HTTP_HEADERS, options.headers)
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -72,10 +77,6 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
shouldInjectTraceHeaders (options, uri) {
|
|
75
|
-
if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
|
|
76
|
-
return false
|
|
77
|
-
}
|
|
78
|
-
|
|
79
80
|
if (!this.config.propagationFilter(uri)) {
|
|
80
81
|
return false
|
|
81
82
|
}
|
|
@@ -212,31 +213,6 @@ function getHooks (config) {
|
|
|
212
213
|
return { request }
|
|
213
214
|
}
|
|
214
215
|
|
|
215
|
-
function hasAmazonSignature (options) {
|
|
216
|
-
if (!options) {
|
|
217
|
-
return false
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (options.headers) {
|
|
221
|
-
const headers = Object.keys(options.headers)
|
|
222
|
-
.reduce((prev, next) => Object.assign(prev, {
|
|
223
|
-
[next.toLowerCase()]: options.headers[next]
|
|
224
|
-
}), {})
|
|
225
|
-
|
|
226
|
-
if (headers['x-amz-signature']) {
|
|
227
|
-
return true
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
|
|
231
|
-
return true
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const search = options.search || options.path
|
|
236
|
-
|
|
237
|
-
return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1
|
|
238
|
-
}
|
|
239
|
-
|
|
240
216
|
function extractSessionDetails (options) {
|
|
241
217
|
if (typeof options === 'string') {
|
|
242
218
|
return new URL(options).host
|
|
@@ -248,8 +224,4 @@ function extractSessionDetails (options) {
|
|
|
248
224
|
return { host, port }
|
|
249
225
|
}
|
|
250
226
|
|
|
251
|
-
function startsWith (searchString) {
|
|
252
|
-
return value => String(value).startsWith(searchString)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
227
|
module.exports = HttpClientPlugin
|
|
@@ -265,6 +265,12 @@ class JestPlugin extends CiPlugin {
|
|
|
265
265
|
})
|
|
266
266
|
})
|
|
267
267
|
|
|
268
|
+
this.addSub('ci:jest:worker-report:logs', (logsPayloads) => {
|
|
269
|
+
JSON.parse(logsPayloads).forEach(({ testConfiguration, logMessage }) => {
|
|
270
|
+
this.tracer._exporter.exportDiLogs(testConfiguration, logMessage)
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
268
274
|
this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
|
|
269
275
|
this.testSuiteSpan.setTag(TEST_STATUS, status)
|
|
270
276
|
if (error) {
|
|
@@ -1,89 +1,21 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
const LangChainTracingPlugin = require('./tracing')
|
|
4
|
+
const LangChainLLMObsPlugin = require('../../dd-trace/src/llmobs/plugins/langchain')
|
|
5
|
+
const CompositePlugin = require('../../dd-trace/src/plugins/composite')
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
const MODEL = 'langchain.request.model'
|
|
9
|
-
const PROVIDER = 'langchain.request.provider'
|
|
10
|
-
const TYPE = 'langchain.request.type'
|
|
11
|
-
|
|
12
|
-
const LangChainHandler = require('./handlers/default')
|
|
13
|
-
const LangChainChatModelHandler = require('./handlers/language_models/chat_model')
|
|
14
|
-
const LangChainLLMHandler = require('./handlers/language_models/llm')
|
|
15
|
-
const LangChainChainHandler = require('./handlers/chain')
|
|
16
|
-
const LangChainEmbeddingHandler = require('./handlers/embedding')
|
|
17
|
-
|
|
18
|
-
class LangChainPlugin extends TracingPlugin {
|
|
7
|
+
class LangChainPlugin extends CompositePlugin {
|
|
19
8
|
static get id () { return 'langchain' }
|
|
20
|
-
static get
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const langchainConfig = this._tracerConfig.langchain || {}
|
|
30
|
-
this.handlers = {
|
|
31
|
-
chain: new LangChainChainHandler(langchainConfig),
|
|
32
|
-
chat_model: new LangChainChatModelHandler(langchainConfig),
|
|
33
|
-
llm: new LangChainLLMHandler(langchainConfig),
|
|
34
|
-
embedding: new LangChainEmbeddingHandler(langchainConfig),
|
|
35
|
-
default: new LangChainHandler(langchainConfig)
|
|
9
|
+
static get plugins () {
|
|
10
|
+
return {
|
|
11
|
+
// ordering here is important - the llm observability plugin must come first
|
|
12
|
+
// so that we can add annotations associated with the span before it finishes.
|
|
13
|
+
// however, because the tracing plugin uses `bindStart` vs the llmobs' `start`,
|
|
14
|
+
// the span is guaranteed to be created in the tracing plugin before the llmobs one is called
|
|
15
|
+
llmobs: LangChainLLMObsPlugin,
|
|
16
|
+
tracing: LangChainTracingPlugin
|
|
36
17
|
}
|
|
37
18
|
}
|
|
38
|
-
|
|
39
|
-
bindStart (ctx) {
|
|
40
|
-
const { resource, type } = ctx
|
|
41
|
-
const handler = this.handlers[type]
|
|
42
|
-
|
|
43
|
-
const instance = ctx.instance
|
|
44
|
-
const apiKey = handler.extractApiKey(instance)
|
|
45
|
-
const provider = handler.extractProvider(instance)
|
|
46
|
-
const model = handler.extractModel(instance)
|
|
47
|
-
|
|
48
|
-
const tags = handler.getSpanStartTags(ctx, provider) || []
|
|
49
|
-
|
|
50
|
-
if (apiKey) tags[API_KEY] = apiKey
|
|
51
|
-
if (provider) tags[PROVIDER] = provider
|
|
52
|
-
if (model) tags[MODEL] = model
|
|
53
|
-
if (type) tags[TYPE] = type
|
|
54
|
-
|
|
55
|
-
const span = this.startSpan('langchain.request', {
|
|
56
|
-
service: this.config.service,
|
|
57
|
-
resource,
|
|
58
|
-
kind: 'client',
|
|
59
|
-
meta: {
|
|
60
|
-
[MEASURED]: 1,
|
|
61
|
-
...tags
|
|
62
|
-
}
|
|
63
|
-
}, false)
|
|
64
|
-
|
|
65
|
-
const store = storage.getStore() || {}
|
|
66
|
-
ctx.currentStore = { ...store, span }
|
|
67
|
-
|
|
68
|
-
return ctx.currentStore
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
asyncEnd (ctx) {
|
|
72
|
-
const span = ctx.currentStore.span
|
|
73
|
-
|
|
74
|
-
const { type } = ctx
|
|
75
|
-
|
|
76
|
-
const handler = this.handlers[type]
|
|
77
|
-
const tags = handler.getSpanEndTags(ctx) || {}
|
|
78
|
-
|
|
79
|
-
span.addTags(tags)
|
|
80
|
-
|
|
81
|
-
span.finish()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
getHandler (type) {
|
|
85
|
-
return this.handlers[type] || this.handlers.default
|
|
86
|
-
}
|
|
87
19
|
}
|
|
88
20
|
|
|
89
21
|
module.exports = LangChainPlugin
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { MEASURED } = require('../../../ext/tags')
|
|
4
|
+
const { storage } = require('../../datadog-core')
|
|
5
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
6
|
+
|
|
7
|
+
const API_KEY = 'langchain.request.api_key'
|
|
8
|
+
const MODEL = 'langchain.request.model'
|
|
9
|
+
const PROVIDER = 'langchain.request.provider'
|
|
10
|
+
const TYPE = 'langchain.request.type'
|
|
11
|
+
|
|
12
|
+
const LangChainHandler = require('./handlers/default')
|
|
13
|
+
const LangChainChatModelHandler = require('./handlers/language_models/chat_model')
|
|
14
|
+
const LangChainLLMHandler = require('./handlers/language_models/llm')
|
|
15
|
+
const LangChainChainHandler = require('./handlers/chain')
|
|
16
|
+
const LangChainEmbeddingHandler = require('./handlers/embedding')
|
|
17
|
+
|
|
18
|
+
class LangChainTracingPlugin extends TracingPlugin {
|
|
19
|
+
static get id () { return 'langchain' }
|
|
20
|
+
static get operation () { return 'invoke' }
|
|
21
|
+
static get system () { return 'langchain' }
|
|
22
|
+
static get prefix () {
|
|
23
|
+
return 'tracing:apm:langchain:invoke'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor () {
|
|
27
|
+
super(...arguments)
|
|
28
|
+
|
|
29
|
+
const langchainConfig = this._tracerConfig.langchain || {}
|
|
30
|
+
this.handlers = {
|
|
31
|
+
chain: new LangChainChainHandler(langchainConfig),
|
|
32
|
+
chat_model: new LangChainChatModelHandler(langchainConfig),
|
|
33
|
+
llm: new LangChainLLMHandler(langchainConfig),
|
|
34
|
+
embedding: new LangChainEmbeddingHandler(langchainConfig),
|
|
35
|
+
default: new LangChainHandler(langchainConfig)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
bindStart (ctx) {
|
|
40
|
+
const { resource, type } = ctx
|
|
41
|
+
const handler = this.handlers[type]
|
|
42
|
+
|
|
43
|
+
const instance = ctx.instance
|
|
44
|
+
const apiKey = handler.extractApiKey(instance)
|
|
45
|
+
const provider = handler.extractProvider(instance)
|
|
46
|
+
const model = handler.extractModel(instance)
|
|
47
|
+
|
|
48
|
+
const tags = handler.getSpanStartTags(ctx, provider) || []
|
|
49
|
+
|
|
50
|
+
if (apiKey) tags[API_KEY] = apiKey
|
|
51
|
+
if (provider) tags[PROVIDER] = provider
|
|
52
|
+
if (model) tags[MODEL] = model
|
|
53
|
+
if (type) tags[TYPE] = type
|
|
54
|
+
|
|
55
|
+
const span = this.startSpan('langchain.request', {
|
|
56
|
+
service: this.config.service,
|
|
57
|
+
resource,
|
|
58
|
+
kind: 'client',
|
|
59
|
+
meta: {
|
|
60
|
+
[MEASURED]: 1,
|
|
61
|
+
...tags
|
|
62
|
+
}
|
|
63
|
+
}, false)
|
|
64
|
+
|
|
65
|
+
const store = storage.getStore() || {}
|
|
66
|
+
ctx.currentStore = { ...store, span }
|
|
67
|
+
|
|
68
|
+
return ctx.currentStore
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
asyncEnd (ctx) {
|
|
72
|
+
const span = ctx.currentStore.span
|
|
73
|
+
|
|
74
|
+
const { type } = ctx
|
|
75
|
+
|
|
76
|
+
const handler = this.handlers[type]
|
|
77
|
+
const tags = handler.getSpanEndTags(ctx) || {}
|
|
78
|
+
|
|
79
|
+
span.addTags(tags)
|
|
80
|
+
|
|
81
|
+
span.finish()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getHandler (type) {
|
|
85
|
+
return this.handlers[type] || this.handlers.default
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = LangChainTracingPlugin
|
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
SSRF: require('./ssrf-analyzer'),
|
|
18
18
|
TEMPLATE_INJECTION_ANALYZER: require('./template-injection-analyzer'),
|
|
19
19
|
UNVALIDATED_REDIRECT_ANALYZER: require('./unvalidated-redirect-analyzer'),
|
|
20
|
+
UNTRUSTED_DESERIALIZATION_ANALYZER: require('./untrusted-deserialization-analyzer'),
|
|
20
21
|
WEAK_CIPHER_ANALYZER: require('./weak-cipher-analyzer'),
|
|
21
22
|
WEAK_HASH_ANALYZER: require('./weak-hash-analyzer'),
|
|
22
23
|
WEAK_RANDOMNESS_ANALYZER: require('./weak-randomness-analyzer'),
|
|
@@ -10,6 +10,8 @@ class CodeInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
10
10
|
|
|
11
11
|
onConfigure () {
|
|
12
12
|
this.addSub('datadog:eval:call', ({ script }) => this.analyze(script))
|
|
13
|
+
this.addSub('datadog:vm:run-script:start', ({ code }) => this.analyze(code))
|
|
14
|
+
this.addSub('datadog:vm:source-text-module:start', ({ code }) => this.analyze(code))
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
_areRangesVulnerable () {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { UNTRUSTED_DESERIALIZATION } = require('../vulnerabilities')
|
|
5
|
+
|
|
6
|
+
class UntrustedDeserializationAnalyzer extends InjectionAnalyzer {
|
|
7
|
+
constructor () {
|
|
8
|
+
super(UNTRUSTED_DESERIALIZATION)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
onConfigure () {
|
|
12
|
+
this.addSub('datadog:node-serialize:unserialize:start', ({ obj }) => this.analyze(obj))
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = new UntrustedDeserializationAnalyzer()
|
|
@@ -25,19 +25,20 @@ class SensitiveHandler {
|
|
|
25
25
|
|
|
26
26
|
this._sensitiveAnalyzers = new Map()
|
|
27
27
|
this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|
|
28
|
-
this._sensitiveAnalyzers.set(vulnerabilities.TEMPLATE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|
|
29
28
|
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, commandSensitiveAnalyzer)
|
|
30
|
-
this._sensitiveAnalyzers.set(vulnerabilities.
|
|
29
|
+
this._sensitiveAnalyzers.set(vulnerabilities.HARDCODED_PASSWORD, (evidence) => {
|
|
30
|
+
return hardcodedPasswordAnalyzer(evidence, this._valuePattern)
|
|
31
|
+
})
|
|
32
|
+
this._sensitiveAnalyzers.set(vulnerabilities.HEADER_INJECTION, (evidence) => {
|
|
33
|
+
return headerSensitiveAnalyzer(evidence, this._namePattern, this._valuePattern)
|
|
34
|
+
})
|
|
31
35
|
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, ldapSensitiveAnalyzer)
|
|
36
|
+
this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer)
|
|
32
37
|
this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, sqlSensitiveAnalyzer)
|
|
33
38
|
this._sensitiveAnalyzers.set(vulnerabilities.SSRF, urlSensitiveAnalyzer)
|
|
39
|
+
this._sensitiveAnalyzers.set(vulnerabilities.TEMPLATE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|
|
40
|
+
this._sensitiveAnalyzers.set(vulnerabilities.UNTRUSTED_DESERIALIZATION, taintedRangeBasedSensitiveAnalyzer)
|
|
34
41
|
this._sensitiveAnalyzers.set(vulnerabilities.UNVALIDATED_REDIRECT, urlSensitiveAnalyzer)
|
|
35
|
-
this._sensitiveAnalyzers.set(vulnerabilities.HEADER_INJECTION, (evidence) => {
|
|
36
|
-
return headerSensitiveAnalyzer(evidence, this._namePattern, this._valuePattern)
|
|
37
|
-
})
|
|
38
|
-
this._sensitiveAnalyzers.set(vulnerabilities.HARDCODED_PASSWORD, (evidence) => {
|
|
39
|
-
return hardcodedPasswordAnalyzer(evidence, this._valuePattern)
|
|
40
|
-
})
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
isSensibleName (name) {
|
|
@@ -15,6 +15,7 @@ module.exports = {
|
|
|
15
15
|
SSRF: 'SSRF',
|
|
16
16
|
TEMPLATE_INJECTION: 'TEMPLATE_INJECTION',
|
|
17
17
|
UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
|
|
18
|
+
UNTRUSTED_DESERIALIZATION: 'UNTRUSTED_DESERIALIZATION',
|
|
18
19
|
WEAK_CIPHER: 'WEAK_CIPHER',
|
|
19
20
|
WEAK_HASH: 'WEAK_HASH',
|
|
20
21
|
WEAK_RANDOMNESS: 'WEAK_RANDOMNESS',
|
|
@@ -19,6 +19,7 @@ class WAFContextWrapper {
|
|
|
19
19
|
this.rulesVersion = rulesVersion
|
|
20
20
|
this.addressesToSkip = new Set()
|
|
21
21
|
this.knownAddresses = knownAddresses
|
|
22
|
+
this.cachedUserIdActions = new Map()
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
run ({ persistent, ephemeral }, raspRule) {
|
|
@@ -27,6 +28,16 @@ class WAFContextWrapper {
|
|
|
27
28
|
return
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
// SPECIAL CASE FOR USER_ID
|
|
32
|
+
// TODO: make this universal
|
|
33
|
+
const userId = persistent?.[addresses.USER_ID] || ephemeral?.[addresses.USER_ID]
|
|
34
|
+
if (userId) {
|
|
35
|
+
const cachedAction = this.cachedUserIdActions.get(userId)
|
|
36
|
+
if (cachedAction) {
|
|
37
|
+
return cachedAction
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
const payload = {}
|
|
31
42
|
let payloadHasData = false
|
|
32
43
|
const newAddressesToSkip = new Set(this.addressesToSkip)
|
|
@@ -79,6 +90,12 @@ class WAFContextWrapper {
|
|
|
79
90
|
|
|
80
91
|
const blockTriggered = !!getBlockingAction(result.actions)
|
|
81
92
|
|
|
93
|
+
// SPECIAL CASE FOR USER_ID
|
|
94
|
+
// TODO: make this universal
|
|
95
|
+
if (userId && ruleTriggered && blockTriggered) {
|
|
96
|
+
this.setUserIdCache(userId, result)
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
Reporter.reportMetrics({
|
|
83
100
|
duration: result.totalRuntime / 1e3,
|
|
84
101
|
durationExt: parseInt(end - start) / 1e3,
|
|
@@ -105,6 +122,26 @@ class WAFContextWrapper {
|
|
|
105
122
|
}
|
|
106
123
|
}
|
|
107
124
|
|
|
125
|
+
setUserIdCache (userId, result) {
|
|
126
|
+
// using old loops for speed
|
|
127
|
+
for (let i = 0; i < result.events.length; i++) {
|
|
128
|
+
const event = result.events[i]
|
|
129
|
+
|
|
130
|
+
for (let j = 0; j < event?.rule_matches?.length; j++) {
|
|
131
|
+
const match = event.rule_matches[j]
|
|
132
|
+
|
|
133
|
+
for (let k = 0; k < match?.parameters?.length; k++) {
|
|
134
|
+
const parameter = match.parameters[k]
|
|
135
|
+
|
|
136
|
+
if (parameter?.address === addresses.USER_ID) {
|
|
137
|
+
this.cachedUserIdActions.set(userId, result.actions)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
108
145
|
dispose () {
|
|
109
146
|
this.ddwafContext.dispose()
|
|
110
147
|
}
|