dd-trace 5.76.0 → 5.77.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.
Files changed (37) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/package.json +14 -14
  3. package/packages/datadog-instrumentations/src/express.js +18 -7
  4. package/packages/datadog-instrumentations/src/jest.js +16 -16
  5. package/packages/datadog-instrumentations/src/router.js +9 -8
  6. package/packages/datadog-plugin-cucumber/src/index.js +2 -1
  7. package/packages/datadog-plugin-grpc/src/util.js +5 -1
  8. package/packages/datadog-plugin-http/src/client.js +5 -1
  9. package/packages/datadog-plugin-http2/src/client.js +5 -1
  10. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  11. package/packages/dd-trace/src/appsec/channels.js +1 -0
  12. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +7 -0
  13. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  14. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -3
  15. package/packages/dd-trace/src/appsec/rasp/lfi.js +23 -9
  16. package/packages/dd-trace/src/appsec/stack_trace.js +6 -6
  17. package/packages/dd-trace/src/config.js +10 -1
  18. package/packages/dd-trace/src/debugger/devtools_client/index.js +8 -1
  19. package/packages/dd-trace/src/debugger/devtools_client/log.js +15 -4
  20. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +5 -1
  21. package/packages/dd-trace/src/exporters/common/docker.js +1 -1
  22. package/packages/dd-trace/src/exporters/span-stats/writer.js +4 -5
  23. package/packages/dd-trace/src/format.js +4 -5
  24. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +9 -6
  25. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +3 -6
  26. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +3 -2
  27. package/packages/dd-trace/src/llmobs/span_processor.js +2 -2
  28. package/packages/dd-trace/src/llmobs/util.js +1 -1
  29. package/packages/dd-trace/src/openfeature/writers/exposures.js +2 -2
  30. package/packages/dd-trace/src/opentelemetry/span.js +3 -2
  31. package/packages/dd-trace/src/plugins/util/serverless.js +1 -2
  32. package/packages/dd-trace/src/plugins/util/test.js +10 -1
  33. package/packages/dd-trace/src/plugins/util/web.js +5 -1
  34. package/packages/dd-trace/src/proxy.js +2 -2
  35. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
  36. package/packages/dd-trace/src/span_processor.js +4 -1
  37. package/packages/dd-trace/src/startup-log.js +24 -38
@@ -11,7 +11,7 @@ require,@opentelemetry/api,Apache license 2.0,Copyright OpenTelemetry Authors
11
11
  require,@opentelemetry/api-logs,Apache license 2.0,Copyright OpenTelemetry Authors
12
12
  require,@opentelemetry/core,Apache license 2.0,Copyright OpenTelemetry Authors
13
13
  require,@opentelemetry/resources,Apache license 2.0,Copyright OpenTelemetry Authors
14
- require,@isaacs/ttlcache,ISC,Copyright (c) 2022-2023 - Isaac Z. Schlueter and Contributors
14
+ require,@isaacs/ttlcache,Blue Oak,Copyright Isaac Z. Schlueter and Contributors
15
15
  require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
16
16
  require,dc-polyfill,MIT,Copyright 2023 Datadog Inc.
17
17
  require,escape-string-regexp,MIT,Copyright Sindre Sorhus
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.76.0",
3
+ "version": "5.77.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -125,15 +125,15 @@
125
125
  "@datadog/native-appsec": "10.3.0",
126
126
  "@datadog/native-iast-taint-tracking": "4.0.0",
127
127
  "@datadog/native-metrics": "3.1.1",
128
- "@datadog/openfeature-node-server": "0.1.0-preview.13",
128
+ "@datadog/openfeature-node-server": "0.1.0-preview.15",
129
129
  "@datadog/pprof": "5.12.0",
130
130
  "@datadog/sketches-js": "2.1.1",
131
- "@datadog/wasm-js-rewriter": "4.0.1",
132
- "@isaacs/ttlcache": "^1.4.1",
131
+ "@datadog/wasm-js-rewriter": "5.0.1",
132
+ "@isaacs/ttlcache": "^2.0.1",
133
133
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
134
134
  "@opentelemetry/api-logs": "<1.0.0",
135
135
  "@opentelemetry/core": ">=1.14.0 <1.31.0",
136
- "@opentelemetry/resources": ">=1.0.0 <1.10.0",
136
+ "@opentelemetry/resources": ">=1.0.0 <1.31.0",
137
137
  "crypto-randomuuid": "^1.0.0",
138
138
  "dc-polyfill": "^0.1.10",
139
139
  "escape-string-regexp": "^5.0.0",
@@ -160,13 +160,13 @@
160
160
  "ttl-set": "^1.0.0"
161
161
  },
162
162
  "devDependencies": {
163
- "@babel/helpers": "^7.27.6",
163
+ "@babel/helpers": "^7.28.4",
164
164
  "@eslint/eslintrc": "^3.3.1",
165
- "@eslint/js": "^9.29.0",
165
+ "@eslint/js": "^9.39.0",
166
166
  "@msgpack/msgpack": "^3.1.2",
167
167
  "@openfeature/core": "^1.9.0",
168
168
  "@openfeature/server-sdk": "^1.20.0",
169
- "@stylistic/eslint-plugin": "^5.0.0",
169
+ "@stylistic/eslint-plugin": "^5.5.0",
170
170
  "@types/chai": "^4.3.16",
171
171
  "@types/mocha": "^10.0.10",
172
172
  "@types/node": "^18.19.106",
@@ -175,15 +175,15 @@
175
175
  "axios": "^1.12.2",
176
176
  "benchmark": "^2.1.4",
177
177
  "body-parser": "^2.2.0",
178
- "bun": "1.3.1",
178
+ "bun": "1.3.2",
179
179
  "chai": "^4.5.0",
180
- "eslint": "^9.29.0",
181
- "eslint-plugin-cypress": "^5.1.0",
180
+ "eslint": "^9.39.0",
181
+ "eslint-plugin-cypress": "^5.2.0",
182
182
  "eslint-plugin-import": "^2.32.0",
183
- "eslint-plugin-mocha": "^11.1.0",
184
- "eslint-plugin-n": "^17.20.0",
183
+ "eslint-plugin-mocha": "^11.2.0",
184
+ "eslint-plugin-n": "^17.23.1",
185
185
  "eslint-plugin-promise": "^7.2.1",
186
- "eslint-plugin-unicorn": "^61.0.2",
186
+ "eslint-plugin-unicorn": "^62.0.0",
187
187
  "express": "^5.1.0",
188
188
  "glob": "^10.4.5",
189
189
  "globals": "^16.3.0",
@@ -53,12 +53,21 @@ function wrapResponseRender (render) {
53
53
  return render.apply(this, arguments)
54
54
  }
55
55
 
56
+ const abortController = new AbortController()
56
57
  return responseRenderChannel.traceSync(
57
- render,
58
+ function () {
59
+ if (abortController.signal.aborted) {
60
+ const error = abortController.signal.reason || new Error('Aborted')
61
+ throw error
62
+ }
63
+
64
+ return render.apply(this, arguments)
65
+ },
58
66
  {
59
67
  req: this.req,
60
68
  view,
61
- options
69
+ options,
70
+ abortController
62
71
  },
63
72
  this,
64
73
  ...arguments
@@ -67,26 +76,28 @@ function wrapResponseRender (render) {
67
76
  }
68
77
 
69
78
  function wrapAppAll (all) {
70
- return function wrappedAll (path, ...otherArgs) {
71
- if (!routeAddedChannel.hasSubscribers) return all.call(this, path, ...otherArgs)
79
+ return function wrappedAll (...args) {
80
+ if (!routeAddedChannel.hasSubscribers) return all.apply(this, args)
72
81
 
82
+ const path = args[0]
73
83
  const paths = normalizeRoutePaths(path)
74
84
 
75
85
  for (const p of paths) {
76
86
  routeAddedChannel.publish({ method: '*', path: p })
77
87
  }
78
88
 
79
- return all.call(this, path, ...otherArgs)
89
+ return all.apply(this, args)
80
90
  }
81
91
  }
82
92
 
83
93
  // Wrap app.route() to instrument Route object
84
94
  function wrapAppRoute (route) {
85
- return function wrappedRoute (path, ...otherArgs) {
86
- const routeObj = route.call(this, path, ...otherArgs)
95
+ return function wrappedRoute (...args) {
96
+ const routeObj = route.apply(this, args)
87
97
 
88
98
  if (!routeAddedChannel.hasSubscribers) return routeObj
89
99
 
100
+ const path = args[0]
90
101
  const paths = normalizeRoutePaths(path)
91
102
 
92
103
  if (!paths.length) return routeObj
@@ -140,7 +140,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
140
140
  this.hasSnapshotTests = undefined
141
141
  this.testSuiteAbsolutePath = context.testPath
142
142
 
143
- this.displayName = config.projectConfig?.displayName?.name
143
+ this.displayName = config.projectConfig?.displayName?.name || config.displayName
144
144
  this.testEnvironmentOptions = getTestEnvironmentOptions(config)
145
145
 
146
146
  const repositoryRoot = this.testEnvironmentOptions._ddRepositoryRoot
@@ -411,23 +411,19 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
411
411
  } else {
412
412
  originalHookFns.set(hook, hookFn)
413
413
  }
414
- // The rule has a bug, see https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2164
415
- // eslint-disable-next-line unicorn/consistent-function-scoping
416
- const wrapperHook = function () {
414
+ const newHookFn = shimmer.wrapFunction(hookFn, hookFn => function () {
417
415
  return testFnCh.runStores(ctx, () => hookFn.apply(this, arguments))
418
- }
419
- // If we don't do this, the timeout will not be triggered
420
- Object.defineProperty(wrapperHook, 'length', { value: hookFn.length })
421
- hook.fn = wrapperHook
416
+ })
417
+ hook.fn = newHookFn
422
418
  }
423
419
  const originalFn = event.test.fn
424
420
  originalTestFns.set(event.test, originalFn)
425
- const wrapper = function () {
426
- return testFnCh.runStores(ctx, () => originalFn.apply(this, arguments))
427
- }
428
- // If we don't do this, the timeout will be not be triggered
429
- Object.defineProperty(wrapper, 'length', { value: originalFn.length })
430
- event.test.fn = wrapper
421
+
422
+ const newFn = shimmer.wrapFunction(event.test.fn, testFn => function () {
423
+ return testFnCh.runStores(ctx, () => testFn.apply(this, arguments))
424
+ })
425
+
426
+ event.test.fn = newFn
431
427
  })
432
428
  }
433
429
 
@@ -1293,7 +1289,7 @@ addHook({
1293
1289
 
1294
1290
  shimmer.wrap(Runtime.prototype, '_createJestObjectFor', _createJestObjectFor => function (from) {
1295
1291
  const result = _createJestObjectFor.apply(this, arguments)
1296
- const suiteFilePath = this._testPath
1292
+ const suiteFilePath = this._testPath || from
1297
1293
 
1298
1294
  shimmer.wrap(result, 'mock', mock => function (moduleName) {
1299
1295
  // If the library is mocked with `jest.mock`, we don't want to bypass jest's own require engine
@@ -1450,7 +1446,11 @@ addHook({
1450
1446
  }, (childProcessWorker) => {
1451
1447
  const ChildProcessWorker = childProcessWorker.default
1452
1448
  shimmer.wrap(ChildProcessWorker.prototype, 'send', sendWrapper)
1453
- shimmer.wrap(ChildProcessWorker.prototype, '_onMessage', onMessageWrapper)
1449
+ if (ChildProcessWorker.prototype._onMessage) {
1450
+ shimmer.wrap(ChildProcessWorker.prototype, '_onMessage', onMessageWrapper)
1451
+ } else if (ChildProcessWorker.prototype.onMessage) {
1452
+ shimmer.wrap(ChildProcessWorker.prototype, 'onMessage', onMessageWrapper)
1453
+ }
1454
1454
  return childProcessWorker
1455
1455
  })
1456
1456
 
@@ -146,33 +146,34 @@ function createWrapRouterMethod (name) {
146
146
  }
147
147
 
148
148
  function wrapMethod (original) {
149
- return shimmer.wrapFunction(original, original => function methodWithTrace (fn, ...otherArgs) {
149
+ return shimmer.wrapFunction(original, original => function methodWithTrace (...args) {
150
150
  let offset = 0
151
151
  if (this.stack) {
152
152
  offset = Array.isArray(this.stack) ? this.stack.length : 1
153
153
  }
154
- const router = original.call(this, fn, ...otherArgs)
154
+ const router = original.apply(this, args)
155
155
 
156
156
  if (typeof this.stack === 'function') {
157
157
  this.stack = [{ handle: this.stack }]
158
158
  }
159
159
 
160
160
  if (routeAddedChannel.hasSubscribers) {
161
- routeAddedChannel.publish({ topOfStackFunc: methodWithTrace, layer: this.stack.at(-1) })
161
+ routeAddedChannel.publish({ topOfStackFunc: methodWithTrace, layer: this.stack?.at(-1) })
162
162
  }
163
163
 
164
+ const fn = args[0]
165
+
164
166
  // Publish only if this router was mounted by app.use() (prevents early '/sub/...')
165
167
  if (routeAddedChannel.hasSubscribers && isAppMounted(this) && this.stack?.length > offset) {
166
168
  // Handle nested router mounting for 'use' method
167
- if (original.name === 'use' && otherArgs.length >= 1) {
169
+ if (original.name === 'use' && args.length >= 2) {
168
170
  const { mountPaths, startIdx } = extractMountPaths(fn)
169
171
 
170
172
  if (mountPaths.length) {
171
173
  const parentPaths = getRouterMountPaths(this)
172
- const callArgs = [fn, ...otherArgs]
173
174
 
174
- for (let i = startIdx; i < callArgs.length; i++) {
175
- const nestedRouter = callArgs[i]
175
+ for (let i = startIdx; i < args.length; i++) {
176
+ const nestedRouter = args[i]
176
177
 
177
178
  if (!nestedRouter || typeof nestedRouter !== 'function') continue
178
179
 
@@ -206,7 +207,7 @@ function createWrapRouterMethod (name) {
206
207
  }
207
208
  }
208
209
 
209
- if (this.stack.length > offset) {
210
+ if (this.stack?.length > offset) {
210
211
  wrapStack(this.stack.slice(offset), extractMatchers(fn))
211
212
  }
212
213
 
@@ -51,7 +51,8 @@ const {
51
51
  } = require('../../dd-trace/src/ci-visibility/telemetry')
52
52
 
53
53
  const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
54
- const BREAKPOINT_SET_GRACE_PERIOD_MS = 200
54
+ const BREAKPOINT_SET_GRACE_PERIOD_MS = 400
55
+
55
56
  const isCucumberWorker = !!getEnvironmentVariable('CUCUMBER_WORKER_ID')
56
57
 
57
58
  class CucumberPlugin extends CiPlugin {
@@ -3,6 +3,10 @@
3
3
  const pick = require('../../datadog-core/src/utils/src/pick')
4
4
  const log = require('../../dd-trace/src/log')
5
5
 
6
+ function getEmptyObject () {
7
+ return {}
8
+ }
9
+
6
10
  module.exports = {
7
11
  getMethodMetadata (path, kind) {
8
12
  const tags = {
@@ -57,6 +61,6 @@ module.exports = {
57
61
  log.error('Expected \'%s\' to be an array or function.', filter)
58
62
  }
59
63
 
60
- return () => ({})
64
+ return getEmptyObject
61
65
  }
62
66
  }
@@ -183,13 +183,17 @@ function normalizeClientConfig (config) {
183
183
  }
184
184
  }
185
185
 
186
+ function is400ErrorCode (code) {
187
+ return code < 400 || code >= 500
188
+ }
189
+
186
190
  function getStatusValidator (config) {
187
191
  if (typeof config.validateStatus === 'function') {
188
192
  return config.validateStatus
189
193
  } else if (config.hasOwnProperty('validateStatus')) {
190
194
  log.error('Expected `validateStatus` to be a function.')
191
195
  }
192
- return code => code < 400 || code >= 500
196
+ return is400ErrorCode
193
197
  }
194
198
 
195
199
  function getFilter (config) {
@@ -162,13 +162,17 @@ function hasAmazonSignature (headers, path) {
162
162
  return false
163
163
  }
164
164
 
165
+ function is400ErrorCode (code) {
166
+ return code < 400 || code >= 500
167
+ }
168
+
165
169
  function getStatusValidator (config) {
166
170
  if (typeof config.validateStatus === 'function') {
167
171
  return config.validateStatus
168
172
  } else if (config.hasOwnProperty('validateStatus')) {
169
173
  log.error('Expected `validateStatus` to be a function.')
170
174
  }
171
- return code => code < 400 || code >= 500
175
+ return is400ErrorCode
172
176
  }
173
177
 
174
178
  function normalizeConfig (config) {
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const TTLCache = require('@isaacs/ttlcache')
3
+ const { TTLCache } = require('@isaacs/ttlcache')
4
4
  const web = require('../plugins/util/web')
5
5
  const log = require('../log')
6
6
  const { AUTO_REJECT, USER_REJECT } = require('../../../../ext/priority')
@@ -12,6 +12,7 @@ module.exports = {
12
12
  cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
13
13
  expressMiddlewareError: dc.channel('apm:express:middleware:error'),
14
14
  expressProcessParams: dc.channel('datadog:express:process_params:start'),
15
+ expressResponseRenderStart: dc.channel('tracing:datadog:express:response:render:start'),
15
16
  expressSession: dc.channel('datadog:express-session:middleware:finish'),
16
17
  fastifyBodyParser: dc.channel('datadog:fastify:body-parser:finish'),
17
18
  fastifyCookieParser: dc.channel('datadog:fastify-cookie:read:finish'),
@@ -68,6 +68,13 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
68
68
  }
69
69
  this.analyze(pathArguments)
70
70
  })
71
+
72
+ this.addSub('tracing:datadog:express:response:render:start', (ctx) => {
73
+ const store = storage('legacy').getStore()
74
+ if (!store) return
75
+
76
+ this.analyze([ctx.view])
77
+ })
71
78
  }
72
79
 
73
80
  _isExcluded (location) {
@@ -8,8 +8,8 @@ const STRINGIFY_SENSITIVE_KEY = STRINGIFY_RANGE_KEY + 'SENSITIVE'
8
8
  const STRINGIFY_SENSITIVE_NOT_STRING_KEY = STRINGIFY_SENSITIVE_KEY + 'NOTSTRING'
9
9
 
10
10
  // eslint-disable-next-line @stylistic/max-len
11
- const KEYS_REGEX_WITH_SENSITIVE_RANGES = new RegExp(`(?:"(${STRINGIFY_RANGE_KEY}_\\d+_))|(?:"(${STRINGIFY_SENSITIVE_KEY}_\\d+_(\\d+)_))|("${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_\\d+_([\\s0-9.a-zA-Z]*)")`, 'gm')
12
- const KEYS_REGEX_WITHOUT_SENSITIVE_RANGES = new RegExp(`"(${STRINGIFY_RANGE_KEY}_\\d+_)`, 'gm')
11
+ const KEYS_REGEX_WITH_SENSITIVE_RANGES = new RegExp(String.raw`(?:"(${STRINGIFY_RANGE_KEY}_\d+_))|(?:"(${STRINGIFY_SENSITIVE_KEY}_\d+_(\d+)_))|("${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_\d+_([\s0-9.a-zA-Z]*)")`, 'gm')
12
+ const KEYS_REGEX_WITHOUT_SENSITIVE_RANGES = new RegExp(String.raw`"(${STRINGIFY_RANGE_KEY}_\d+_)`, 'gm')
13
13
 
14
14
  const sensitiveValueRegex = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
15
15
 
@@ -84,9 +84,10 @@ function sendVulnerabilities (vulnerabilities, span) {
84
84
  const jsonToSend = vulnerabilitiesFormatter.toJson(validatedVulnerabilities)
85
85
 
86
86
  if (jsonToSend.vulnerabilities.length > 0) {
87
- const tags = {}
88
- // TODO: Store this outside of the span and set the tag in the exporter.
89
- tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
87
+ const tags = {
88
+ // TODO: Store this outside of the span and set the tag in the exporter.
89
+ [IAST_JSON_TAG_KEY]: JSON.stringify(jsonToSend)
90
+ }
90
91
  span.addTags(tags)
91
92
  }
92
93
  }
@@ -1,12 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { fsOperationStart, incomingHttpRequestStart } = require('../channels')
3
+ const { isAbsolute } = require('path')
4
+
5
+ const { fsOperationStart, incomingHttpRequestStart, expressResponseRenderStart } = require('../channels')
4
6
  const { storage } = require('../../../../datadog-core')
5
7
  const { enable: enableFsPlugin, disable: disableFsPlugin, RASP_MODULE } = require('./fs-plugin')
6
8
  const { FS_OPERATION_PATH } = require('../addresses')
7
9
  const waf = require('../waf')
8
10
  const { RULE_TYPES, handleResult } = require('./utils')
9
- const { isAbsolute } = require('path')
10
11
 
11
12
  let config
12
13
  let enabled
@@ -25,6 +26,7 @@ function enable (_config) {
25
26
  function disable () {
26
27
  if (fsOperationStart.hasSubscribers) fsOperationStart.unsubscribe(analyzeLfi)
27
28
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(onFirstReceivedRequest)
29
+ if (expressResponseRenderStart.hasSubscribers) expressResponseRenderStart.unsubscribe(analyzeLfiInResponseRender)
28
30
 
29
31
  disableFsPlugin(RASP_MODULE)
30
32
 
@@ -42,10 +44,18 @@ function onFirstReceivedRequest () {
42
44
 
43
45
  if (!analyzeSubscribed) {
44
46
  fsOperationStart.subscribe(analyzeLfi)
47
+ expressResponseRenderStart.subscribe(analyzeLfiInResponseRender)
45
48
  analyzeSubscribed = true
46
49
  }
47
50
  }
48
51
 
52
+ function analyzeLfiInResponseRender (ctx) {
53
+ const store = storage('legacy').getStore()
54
+ if (!store) return
55
+
56
+ analyzeLfiPath(ctx.view, ctx.req, store.res, ctx.abortController)
57
+ }
58
+
49
59
  function analyzeLfi (ctx) {
50
60
  const store = storage('legacy').getStore()
51
61
  if (!store) return
@@ -54,15 +64,19 @@ function analyzeLfi (ctx) {
54
64
  if (!req || !fs) return
55
65
 
56
66
  getPaths(ctx, fs).forEach(path => {
57
- const ephemeral = {
58
- [FS_OPERATION_PATH]: path
59
- }
67
+ analyzeLfiPath(path, req, res, ctx.abortController)
68
+ })
69
+ }
60
70
 
61
- const raspRule = { type: RULE_TYPES.LFI }
71
+ function analyzeLfiPath (path, req, res, abortController) {
72
+ const ephemeral = {
73
+ [FS_OPERATION_PATH]: path
74
+ }
62
75
 
63
- const result = waf.run({ ephemeral }, req, raspRule)
64
- handleResult(result, req, res, ctx.abortController, config, raspRule)
65
- })
76
+ const raspRule = { type: RULE_TYPES.LFI }
77
+
78
+ const result = waf.run({ ephemeral }, req, raspRule)
79
+ handleResult(result, req, res, abortController, config, raspRule)
66
80
  }
67
81
 
68
82
  function getPaths (ctx, fs) {
@@ -42,9 +42,9 @@ function filterOutFramesFromLibrary (callSiteList) {
42
42
  if (globalThis.__DD_ESBUILD_IAST_WITH_SM) {
43
43
  // bundled with SourceMap, get original file and line to discriminate if comes from dd-trace or not
44
44
  const callSiteLocation = {
45
- path: callSite.getFileName(),
46
- line: callSite.getLineNumber(),
47
- column: callSite.getColumnNumber()
45
+ path: callSite.getTranslatedFileName?.() ?? callSite.getFileName(),
46
+ line: callSite.getTranslatedLineNumber?.() ?? callSite.getLineNumber(),
47
+ column: callSite.getTranslatedColumnNumber?.() ?? callSite.getColumnNumber()
48
48
  }
49
49
  const { path } = getOriginalPathAndLineFromSourceMap(callSiteLocation)
50
50
  return !path?.startsWith(ddBasePath)
@@ -68,9 +68,9 @@ function getCallsiteFrames (maxDepth = 32, constructorOpt = getCallsiteFrames, c
68
68
  const callSite = filteredFrames[index]
69
69
  indexedFrames.push({
70
70
  id: index,
71
- file: callSite.getFileName(),
72
- line: callSite.getLineNumber(),
73
- column: callSite.getColumnNumber(),
71
+ file: callSite.getTranslatedFileName?.() ?? callSite.getFileName(),
72
+ line: callSite.getTranslatedLineNumber?.() ?? callSite.getLineNumber(),
73
+ column: callSite.getTranslatedColumnNumber?.() ?? callSite.getColumnNumber(),
74
74
  function: callSite.getFunctionName(),
75
75
  class_name: callSite.getTypeName(),
76
76
  isNative: callSite.isNative()
@@ -4,6 +4,7 @@ const fs = require('fs')
4
4
  const os = require('os')
5
5
  const uuid = require('crypto-randomuuid') // we need to keep the old uuid dep because of cypress
6
6
  const { URL } = require('url')
7
+
7
8
  const log = require('./log')
8
9
  const tagger = require('./tagger')
9
10
  const set = require('../../datadog-core/src/utils/src/set')
@@ -1507,4 +1508,12 @@ function getAgentUrl (url, options) {
1507
1508
  }
1508
1509
  }
1509
1510
 
1510
- module.exports = Config
1511
+ let configInstance = null
1512
+ function getConfig (options) {
1513
+ if (!configInstance) {
1514
+ configInstance = new Config(options)
1515
+ }
1516
+ return configInstance
1517
+ }
1518
+
1519
+ module.exports = getConfig
@@ -58,6 +58,11 @@ if (SUPPORT_ARRAY_BUFFER_RESIZE) {
58
58
  session.on('Debugger.paused', async ({ params }) => {
59
59
  const start = process.hrtime.bigint()
60
60
 
61
+ if (params.reason !== 'other') {
62
+ // This error should not be caught, and should exit the worker thread, effectively stopping the debugging session
63
+ throw new Error(`Unexpected Debugger.paused reason: ${params.reason}`)
64
+ }
65
+
61
66
  let maxReferenceDepth, maxCollectionSize, maxFieldCount, maxLength
62
67
  let sampled = false
63
68
  let numberOfProbesWithSnapshots = 0
@@ -168,7 +173,9 @@ session.on('Debugger.paused', async ({ params }) => {
168
173
  await session.post('Debugger.resume')
169
174
  const diff = process.hrtime.bigint() - start // TODO: Recored as telemetry (DEBUG-2858)
170
175
 
171
- log.debug(() => `[debugger:devtools_client] Finished processing breakpoints - main thread paused for: ${
176
+ // This doesn't measure the overhead of the CDP protocol. The actual pause time is slightly larger.
177
+ // On my machine I'm seeing around 1.7ms of overhead.
178
+ log.debug(() => `[debugger:devtools_client] Finished processing breakpoints - main thread paused for: ~${
172
179
  Number(diff) / 1_000_000
173
180
  } ms`)
174
181
 
@@ -1,10 +1,15 @@
1
1
  'use strict'
2
2
 
3
+ /** @typedef {'error'|'warn'|'info'|'debug'} Level */
4
+ /** @typedef {(...args: unknown[]) => void} LogFn */
5
+ /** @typedef {Record<Level, LogFn>} Logger */
6
+
3
7
  const { workerData } = require('node:worker_threads')
4
8
 
5
9
  // For testing purposes, we allow `workerData` to be undefined and fallback to a default config
6
- const { config: { debug, logLevel }, logPort } = workerData ?? { config: { debug: false } }
10
+ const { config: { debug = false, logLevel } = {}, logPort } = workerData ?? {}
7
11
 
12
+ /** @type {Level[]} */
8
13
  const LEVELS = ['error', 'warn', 'info', 'debug']
9
14
  const on = (level, ...args) => {
10
15
  if (typeof args[0] === 'function') {
@@ -14,6 +19,12 @@ const on = (level, ...args) => {
14
19
  }
15
20
  const off = () => {}
16
21
 
17
- for (const level of LEVELS) {
18
- module.exports[level] = debug && LEVELS.indexOf(logLevel) >= LEVELS.indexOf(level) ? on.bind(null, level) : off
19
- }
22
+ const threshold = LEVELS.indexOf(logLevel)
23
+
24
+ /** @type {Logger} */
25
+ module.exports = Object.fromEntries(
26
+ LEVELS.map(level => [
27
+ level,
28
+ debug && threshold >= LEVELS.indexOf(level) ? on.bind(null, level) : off
29
+ ])
30
+ )
@@ -13,6 +13,10 @@ module.exports = {
13
13
  getLocalStateForCallFrame
14
14
  }
15
15
 
16
+ function returnError () {
17
+ return new Error('Error getting local state')
18
+ }
19
+
16
20
  async function getLocalStateForCallFrame (
17
21
  callFrame,
18
22
  {
@@ -37,7 +41,7 @@ async function getLocalStateForCallFrame (
37
41
  // TODO: We might be able to get part of the scope chain.
38
42
  // Consider if we could set errors just for the part of the scope chain that throws during collection.
39
43
  log.error('[debugger:devtools_client] Error getting local state for call frame', err)
40
- return () => new Error('Error getting local state')
44
+ return returnError
41
45
  }
42
46
 
43
47
  // Delay calling `processRawState` so the caller gets a chance to resume the main thread before processing `rawState`
@@ -12,7 +12,7 @@ const uuidSource =
12
12
  const containerSource = '[0-9a-f]{64}'
13
13
  const taskSource = String.raw`[0-9a-f]{32}-\d+`
14
14
  const lineReg = /^(\d+):([^:]*):(.+)$/m
15
- const entityReg = new RegExp(`.*(${uuidSource}|${containerSource}|${taskSource})(?:\\.scope)?$`, 'm')
15
+ const entityReg = new RegExp(String.raw`.*(${uuidSource}|${containerSource}|${taskSource})(?:\.scope)?$`, 'm')
16
16
 
17
17
  let inode = 0
18
18
  let cgroup = ''
@@ -36,13 +36,12 @@ function makeRequest (data, url, cb) {
36
36
  'Datadog-Meta-Lang': 'javascript',
37
37
  'Datadog-Meta-Tracer-Version': pkg.version,
38
38
  'Content-Type': 'application/msgpack'
39
- }
39
+ },
40
+ protocol: url.protocol,
41
+ hostname: url.hostname,
42
+ port: url.port
40
43
  }
41
44
 
42
- options.protocol = url.protocol
43
- options.hostname = url.hostname
44
- options.port = url.port
45
-
46
45
  log.debug('Request to the intake: %j', options)
47
46
 
48
47
  request(data, options, (err, res) => {
@@ -32,13 +32,13 @@ const map = {
32
32
  'resource.name': 'resource'
33
33
  }
34
34
 
35
- function format (span) {
35
+ function format (span, isChunkRoot) {
36
36
  const formatted = formatSpan(span)
37
37
 
38
38
  extractSpanLinks(formatted, span)
39
39
  extractSpanEvents(formatted, span)
40
40
  extractRootTags(formatted, span)
41
- extractChunkTags(formatted, span)
41
+ extractChunkTags(formatted, span, isChunkRoot)
42
42
  extractTags(formatted, span)
43
43
 
44
44
  return formatted
@@ -192,11 +192,10 @@ function extractRootTags (formattedSpan, span) {
192
192
  addTag({}, formattedSpan.metrics, TOP_LEVEL_KEY, 1)
193
193
  }
194
194
 
195
- function extractChunkTags (formattedSpan, span) {
195
+ function extractChunkTags (formattedSpan, span, isChunkRoot) {
196
196
  const context = span.context()
197
- const isLocalRoot = span === context._trace.started[0]
198
197
 
199
- if (!isLocalRoot) return
198
+ if (!isChunkRoot) return
200
199
 
201
200
  for (const [key, value] of Object.entries(context._trace.tags)) {
202
201
  addTag(formattedSpan.meta, formattedSpan.metrics, key, value)
@@ -82,7 +82,9 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
82
82
  * @param {string} toolDescription
83
83
  * @returns {string | undefined}
84
84
  */
85
- findToolName (toolDescription) {
85
+ findToolName (toolName, toolDescription) {
86
+ if (Number.isNaN(Number.parseInt(toolName))) return toolName
87
+
86
88
  for (const availableTool of this.#availableTools) {
87
89
  const description = availableTool.description
88
90
  if (description === toolDescription && availableTool.id) {
@@ -260,16 +262,17 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
260
262
 
261
263
  const formattedToolCalls = []
262
264
  for (const toolCall of outputMessageToolCalls) {
263
- const toolCallArgs = getJsonStringValue(toolCall.args, {})
265
+ const toolArgs = toolCall.args ?? toolCall.input
266
+ const toolCallArgs = typeof toolArgs === 'string' ? getJsonStringValue(toolArgs, {}) : toolArgs
264
267
  const toolDescription = toolsForModel?.find(tool => toolCall.toolName === tool.name)?.description
265
- const name = this.findToolName(toolDescription)
268
+ const name = this.findToolName(toolCall.toolName, toolDescription)
266
269
  this.#toolCallIdsToName[toolCall.toolCallId] = name
267
270
 
268
271
  formattedToolCalls.push({
269
272
  arguments: toolCallArgs,
270
273
  name,
271
274
  toolId: toolCall.toolCallId,
272
- type: 'function'
275
+ type: toolCall.toolCallType ?? 'function'
273
276
  })
274
277
  }
275
278
 
@@ -317,10 +320,10 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
317
320
  finalContent += part.text ?? part.data
318
321
  } else if (type === 'tool-call') {
319
322
  const toolDescription = toolsForModel?.find(tool => part.toolName === tool.name)?.description
320
- const name = this.findToolName(toolDescription)
323
+ const name = this.findToolName(part.toolName, toolDescription)
321
324
 
322
325
  toolCalls.push({
323
- arguments: part.args,
326
+ arguments: part.args ?? part.input,
324
327
  name,
325
328
  toolId: part.toolCallId,
326
329
  type: 'function'
@@ -242,12 +242,9 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
242
242
  const cacheWriteTokens = usage.cache_creation_input_tokens
243
243
  const cacheReadTokens = usage.cache_read_input_tokens
244
244
 
245
- const metrics = {}
246
-
247
- metrics.inputTokens =
248
- (inputTokens ?? 0) +
249
- (cacheWriteTokens ?? 0) +
250
- (cacheReadTokens ?? 0)
245
+ const metrics = {
246
+ inputTokens: (inputTokens ?? 0) + (cacheWriteTokens ?? 0) + (cacheReadTokens ?? 0)
247
+ }
251
248
 
252
249
  if (outputTokens) metrics.outputTokens = outputTokens
253
250
  const totalTokens = metrics.inputTokens + (outputTokens ?? 0)
@@ -37,8 +37,9 @@ class LangChainLLMObsHandler {
37
37
  return message
38
38
  }
39
39
  try {
40
- const messageContent = {}
41
- messageContent.content = message.content || ''
40
+ const messageContent = {
41
+ content: message.content || ''
42
+ }
42
43
 
43
44
  const role = this.getRole(message)
44
45
  if (role) messageContent.role = role
@@ -203,8 +203,8 @@ class LLMObsSpanProcessor {
203
203
  // This function can be reused for other fields if needed
204
204
  // Messages, Documents, and Metrics are safeguarded in `llmobs/tagger.js`
205
205
  #addObject (obj, carrier) {
206
- const seenObjects = new WeakSet()
207
- seenObjects.add(obj) // capture root object
206
+ // Capture root object by default
207
+ const seenObjects = new WeakSet([obj])
208
208
 
209
209
  const isCircular = value => {
210
210
  if (typeof value !== 'object') return false
@@ -6,7 +6,7 @@ function encodeUnicode (str = '') {
6
6
  let result = ''
7
7
  for (let i = 0; i < str.length; i++) {
8
8
  const code = str.charCodeAt(i)
9
- result += code > 127 ? `\\u${code.toString(16).padStart(4, '0')}` : str[i]
9
+ result += code > 127 ? String.raw`\u${code.toString(16).padStart(4, '0')}` : str[i]
10
10
  }
11
11
  return result
12
12
  }
@@ -27,7 +27,7 @@ const {
27
27
 
28
28
  /**
29
29
  * @typedef {Object} ExposureContext
30
- * @property {string} service_name - Service name
30
+ * @property {string} service - Service name
31
31
  * @property {string} [version] - Service version
32
32
  * @property {string} [env] - Service environment
33
33
  */
@@ -127,7 +127,7 @@ class ExposuresWriter extends BaseFFEWriter {
127
127
  */
128
128
  _buildContext () {
129
129
  const context = {
130
- service_name: this._config.service || 'unknown'
130
+ service: this._config.service || 'unknown'
131
131
  }
132
132
 
133
133
  // Only include version and env if they are defined
@@ -10,7 +10,7 @@ const { timeInputToHrTime } = require('@opentelemetry/core')
10
10
  const tracer = require('../../')
11
11
  const DatadogSpan = require('../opentracing/span')
12
12
  const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK, IGNORE_OTEL_ERROR } = require('../constants')
13
- const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
13
+ const { SERVICE_NAME, RESOURCE_NAME, SPAN_KIND } = require('../../../../ext/tags')
14
14
  const kinds = require('../../../../ext/kinds')
15
15
 
16
16
  const SpanContext = require('./span_context')
@@ -146,7 +146,8 @@ class Span {
146
146
  integrationName: parentTracer?._isOtelLibrary ? 'otel.library' : 'otel',
147
147
  tags: {
148
148
  [SERVICE_NAME]: _tracer._service,
149
- [RESOURCE_NAME]: spanName
149
+ [RESOURCE_NAME]: spanName,
150
+ [SPAN_KIND]: spanKindNames[kind]
150
151
  },
151
152
  links
152
153
  }, _tracer._debug)
@@ -3,7 +3,6 @@
3
3
  const types = require('../../../../../ext/types')
4
4
  const web = require('./web')
5
5
 
6
- const serverless = { ...web }
7
- serverless.TYPE = types.SERVERLESS
6
+ const serverless = { ...web, TYPE: types.SERVERLESS }
8
7
 
9
8
  module.exports = serverless
@@ -627,20 +627,29 @@ function getCodeOwnersFileEntries (rootDir) {
627
627
  return entries.reverse()
628
628
  }
629
629
 
630
+ const codeOwnersPerFileName = new Map()
631
+
630
632
  function getCodeOwnersForFilename (filename, entries) {
631
633
  if (!entries) {
632
634
  return null
633
635
  }
636
+ if (codeOwnersPerFileName.has(filename)) {
637
+ return codeOwnersPerFileName.get(filename)
638
+ }
634
639
  for (const entry of entries) {
635
640
  try {
636
641
  const isResponsible = ignore().add(entry.pattern).ignores(filename)
637
642
  if (isResponsible) {
638
- return JSON.stringify(entry.owners)
643
+ const codeOwners = JSON.stringify(entry.owners)
644
+ codeOwnersPerFileName.set(filename, codeOwners)
645
+ return codeOwners
639
646
  }
640
647
  } catch {
648
+ codeOwnersPerFileName.set(filename, null)
641
649
  return null
642
650
  }
643
651
  }
652
+ codeOwnersPerFileName.set(filename, null)
644
653
  return null
645
654
  }
646
655
 
@@ -577,13 +577,17 @@ function getHeadersToRecord (config) {
577
577
  return []
578
578
  }
579
579
 
580
+ function isNot500ErrorCode (code) {
581
+ return code < 500
582
+ }
583
+
580
584
  function getStatusValidator (config) {
581
585
  if (typeof config.validateStatus === 'function') {
582
586
  return config.validateStatus
583
587
  } else if (config.hasOwnProperty('validateStatus')) {
584
588
  log.error('Expected `validateStatus` to be a function.')
585
589
  }
586
- return code => code < 500
590
+ return isNot500ErrorCode
587
591
  }
588
592
 
589
593
  const noop = () => {}
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
  const NoopProxy = require('./noop/proxy')
3
3
  const DatadogTracer = require('./tracer')
4
- const Config = require('./config')
4
+ const getConfig = require('./config')
5
5
  const runtimeMetrics = require('./runtime_metrics')
6
6
  const log = require('./log')
7
7
  const { setStartupLogPluginManager } = require('./startup-log')
@@ -98,7 +98,7 @@ class Tracer extends NoopProxy {
98
98
  this._initialized = true
99
99
 
100
100
  try {
101
- const config = new Config(options) // TODO: support dynamic code config
101
+ const config = getConfig(options) // TODO: support dynamic code config
102
102
 
103
103
  if (config.crashtracking.enabled) {
104
104
  require('./crashtracking').start(config)
@@ -22,7 +22,7 @@ let nativeMetrics = null
22
22
  let gcObserver = null
23
23
  let interval = null
24
24
  let client = null
25
- let lastTime = 0n
25
+ let lastTime = 0
26
26
  let lastCpuUsage = null
27
27
  let eventLoopDelayObserver = null
28
28
 
@@ -103,7 +103,6 @@ module.exports = {
103
103
  interval = null
104
104
 
105
105
  client = null
106
- lastTime = 0n
107
106
  lastCpuUsage = null
108
107
 
109
108
  gcObserver?.disconnect()
@@ -339,6 +338,7 @@ function startGCObserver () {
339
338
 
340
339
  gcObserver = new PerformanceObserver(list => {
341
340
  for (const entry of list.getEntries()) {
341
+ // @ts-expect-error - entry.detail?.kind and entry.kind are not typed
342
342
  const type = gcType(entry.detail?.kind || entry.kind)
343
343
  const duration = entry.duration * 1_000_000
344
344
 
@@ -47,11 +47,14 @@ class SpanProcessor {
47
47
  this._spanSampler.sample(spanContext)
48
48
  this._gitMetadataTagger.tagGitMetadata(spanContext)
49
49
 
50
+ let isChunkRoot = true
51
+
50
52
  for (const span of started) {
51
53
  if (span._duration === undefined) {
52
54
  active.push(span)
53
55
  } else {
54
- const formattedSpan = format(span)
56
+ const formattedSpan = format(span, isChunkRoot)
57
+ isChunkRoot = false
55
58
  this._stats?.onSpanFinished(formattedSpan)
56
59
  formatted.push(formattedSpan)
57
60
 
@@ -10,18 +10,10 @@ const tracerVersion = require('../../../package.json').version
10
10
  const errors = {}
11
11
  let config
12
12
  let pluginManager
13
+ /** @type {import('./sampling_rule')[]} */
13
14
  let samplingRules = []
14
15
  let alreadyRan = false
15
16
 
16
- /**
17
- * @returns {Record<string, unknown>}
18
- */
19
- function getIntegrationsAndAnalytics () {
20
- return {
21
- integrations_loaded: Object.keys(pluginManager._pluginsByName)
22
- }
23
- }
24
-
25
17
  /**
26
18
  * @param {{ agentError: { code: string, message: string } }} [options]
27
19
  */
@@ -70,36 +62,30 @@ function tracerInfo () {
70
62
  return JSON.stringify(this, (_key_, value) => {
71
63
  return typeof value === 'bigint' || typeof value === 'symbol' ? String(value) : value
72
64
  })
73
- }
74
- }
75
-
76
- out.date = new Date().toISOString()
77
- out.os_name = os.type()
78
- out.os_version = os.release()
79
- out.architecture = os.arch()
80
- out.version = tracerVersion
81
- out.lang = 'nodejs'
82
- out.lang_version = process.versions.node
83
- out.env = config.env
84
- out.enabled = config.enabled
85
- out.service = config.service
86
- out.agent_url = url
87
- out.debug = !!config.debug
88
- out.sample_rate = config.sampler.sampleRate
89
- out.sampling_rules = samplingRules
90
- out.tags = config.tags
91
- if (config.tags && config.tags.version) {
92
- out.dd_version = config.tags.version
65
+ },
66
+ date: new Date().toISOString(),
67
+ os_name: os.type(),
68
+ os_version: os.release(),
69
+ architecture: os.arch(),
70
+ version: tracerVersion,
71
+ lang: 'nodejs',
72
+ lang_version: process.versions.node,
73
+ env: config.env,
74
+ enabled: config.enabled,
75
+ service: config.service,
76
+ agent_url: url,
77
+ debug: !!config.debug,
78
+ sample_rate: config.sampler.sampleRate,
79
+ sampling_rules: samplingRules,
80
+ tags: config.tags,
81
+ ...(config.tags && config.tags.version && { dd_version: config.tags.version }),
82
+ log_injection_enabled: !!config.logInjection,
83
+ runtime_metrics_enabled: !!config.runtimeMetrics,
84
+ profiling_enabled: config.profiling?.enabled === 'true' || config.profiling?.enabled === 'auto',
85
+ integrations_loaded: Object.keys(pluginManager._pluginsByName),
86
+ appsec_enabled: !!config.appsec.enabled,
93
87
  }
94
88
 
95
- out.log_injection_enabled = !!config.logInjection
96
- out.runtime_metrics_enabled = !!config.runtimeMetrics
97
- const profilingEnabled = config.profiling?.enabled
98
- out.profiling_enabled = profilingEnabled === 'true' || profilingEnabled === 'auto'
99
- Object.assign(out, getIntegrationsAndAnalytics())
100
-
101
- out.appsec_enabled = !!config.appsec.enabled
102
-
103
89
  return out
104
90
  }
105
91
 
@@ -118,7 +104,7 @@ function setStartupLogPluginManager (thePluginManager) {
118
104
  }
119
105
 
120
106
  /**
121
- * @param {import('./sampling_rule')} theRules
107
+ * @param {import('./sampling_rule')[]} theRules
122
108
  */
123
109
  function setSamplingRules (theRules) {
124
110
  samplingRules = theRules