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.
Files changed (43) hide show
  1. package/package.json +3 -3
  2. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  3. package/packages/datadog-instrumentations/src/jest.js +11 -3
  4. package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
  5. package/packages/datadog-instrumentations/src/openai.js +2 -0
  6. package/packages/datadog-instrumentations/src/vitest.js +25 -12
  7. package/packages/datadog-instrumentations/src/vm.js +49 -0
  8. package/packages/datadog-plugin-http/src/client.js +5 -33
  9. package/packages/datadog-plugin-jest/src/index.js +6 -0
  10. package/packages/datadog-plugin-langchain/src/index.js +12 -80
  11. package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
  12. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  13. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
  14. package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
  15. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
  16. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  17. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
  18. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +10 -3
  19. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
  20. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
  21. package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
  22. package/packages/dd-trace/src/crashtracking/noop.js +3 -0
  23. package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -3
  24. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -8
  25. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +1 -6
  26. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  27. package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
  28. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
  29. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
  30. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
  31. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
  32. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
  33. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
  34. package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
  35. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  36. package/packages/dd-trace/src/llmobs/util.js +7 -1
  37. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
  38. package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
  39. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -2
  40. package/packages/dd-trace/src/plugins/util/test.js +6 -6
  41. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
  42. package/packages/dd-trace/src/profiling/profiler.js +11 -8
  43. 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-71c4307",
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/**/*.spec.js\"",
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.3.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 >= 1
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
+ })
@@ -338,6 +338,8 @@ for (const shim of V4_PACKAGE_SHIMS) {
338
338
  })
339
339
  })
340
340
 
341
+ ch.end.publish(ctx)
342
+
341
343
  return apiProm
342
344
  })
343
345
  })
@@ -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 testFilepaths = await this.ctx.getTestFilepaths()
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 (testPath) {
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: testPath[0], frameworkVersion })
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 { MEASURED } = require('../../../ext/tags')
4
- const { storage } = require('../../datadog-core')
5
- const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
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
- 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 LangChainPlugin extends TracingPlugin {
7
+ class LangChainPlugin extends CompositePlugin {
19
8
  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)
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.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer)
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
  }