dd-trace 5.51.0 → 5.53.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 (98) hide show
  1. package/LICENSE-3rdparty.csv +0 -6
  2. package/README.md +5 -0
  3. package/index.d.ts +88 -6
  4. package/package.json +3 -9
  5. package/packages/datadog-instrumentations/src/amqplib.js +8 -5
  6. package/packages/datadog-instrumentations/src/child_process.js +2 -1
  7. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +406 -0
  8. package/packages/datadog-instrumentations/src/couchbase.js +2 -1
  9. package/packages/datadog-instrumentations/src/cucumber.js +43 -45
  10. package/packages/datadog-instrumentations/src/dns.js +16 -14
  11. package/packages/datadog-instrumentations/src/express.js +2 -6
  12. package/packages/datadog-instrumentations/src/fs.js +43 -51
  13. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  14. package/packages/datadog-instrumentations/src/helpers/register.js +17 -12
  15. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  16. package/packages/datadog-instrumentations/src/iovalkey.js +51 -0
  17. package/packages/datadog-instrumentations/src/jest.js +53 -40
  18. package/packages/datadog-instrumentations/src/kafkajs.js +21 -8
  19. package/packages/datadog-instrumentations/src/mocha/main.js +33 -46
  20. package/packages/datadog-instrumentations/src/mocha/utils.js +76 -74
  21. package/packages/datadog-instrumentations/src/mysql2.js +3 -1
  22. package/packages/datadog-instrumentations/src/net.js +27 -29
  23. package/packages/datadog-instrumentations/src/next.js +6 -14
  24. package/packages/datadog-instrumentations/src/pg.js +15 -7
  25. package/packages/datadog-instrumentations/src/playwright.js +64 -67
  26. package/packages/datadog-instrumentations/src/url.js +9 -17
  27. package/packages/datadog-instrumentations/src/vitest.js +66 -72
  28. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/batch-consumer.js +11 -0
  29. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/consumer.js +11 -0
  30. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/index.js +19 -0
  31. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/producer.js +11 -0
  32. package/packages/datadog-plugin-cucumber/src/index.js +32 -18
  33. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -0
  34. package/packages/datadog-plugin-dns/src/lookup.js +10 -5
  35. package/packages/datadog-plugin-dns/src/lookup_service.js +6 -2
  36. package/packages/datadog-plugin-dns/src/resolve.js +5 -2
  37. package/packages/datadog-plugin-dns/src/reverse.js +6 -2
  38. package/packages/datadog-plugin-fs/src/index.js +9 -2
  39. package/packages/datadog-plugin-iovalkey/src/index.js +18 -0
  40. package/packages/datadog-plugin-jest/src/index.js +17 -8
  41. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -1
  42. package/packages/datadog-plugin-kafkajs/src/consumer.js +12 -21
  43. package/packages/datadog-plugin-kafkajs/src/producer.js +12 -5
  44. package/packages/datadog-plugin-kafkajs/src/utils.js +27 -0
  45. package/packages/datadog-plugin-langchain/src/index.js +0 -1
  46. package/packages/datadog-plugin-mocha/src/index.js +58 -35
  47. package/packages/datadog-plugin-net/src/ipc.js +6 -4
  48. package/packages/datadog-plugin-net/src/tcp.js +15 -9
  49. package/packages/datadog-plugin-pg/src/index.js +5 -1
  50. package/packages/datadog-plugin-playwright/src/index.js +29 -20
  51. package/packages/datadog-plugin-redis/src/index.js +8 -3
  52. package/packages/datadog-plugin-vitest/src/index.js +67 -44
  53. package/packages/datadog-shimmer/src/shimmer.js +164 -33
  54. package/packages/dd-trace/src/appsec/api_security_sampler.js +20 -12
  55. package/packages/dd-trace/src/appsec/graphql.js +2 -2
  56. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +14 -9
  57. package/packages/dd-trace/src/appsec/index.js +15 -12
  58. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  59. package/packages/dd-trace/src/appsec/rasp/utils.js +11 -6
  60. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  61. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -2
  62. package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -9
  63. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -6
  64. package/packages/dd-trace/src/baggage.js +36 -0
  65. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
  66. package/packages/dd-trace/src/config.js +14 -2
  67. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +61 -7
  68. package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -26
  69. package/packages/dd-trace/src/debugger/devtools_client/send.js +8 -7
  70. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +15 -7
  71. package/packages/dd-trace/src/debugger/devtools_client/state.js +22 -2
  72. package/packages/dd-trace/src/dogstatsd.js +2 -0
  73. package/packages/dd-trace/src/exporters/common/docker.js +13 -31
  74. package/packages/dd-trace/src/guardrails/telemetry.js +2 -5
  75. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  76. package/packages/dd-trace/src/llmobs/writers/base.js +33 -12
  77. package/packages/dd-trace/src/noop/proxy.js +5 -0
  78. package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -0
  79. package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -9
  80. package/packages/dd-trace/src/plugin_manager.js +2 -0
  81. package/packages/dd-trace/src/plugins/index.js +4 -0
  82. package/packages/dd-trace/src/plugins/log_plugin.js +9 -20
  83. package/packages/dd-trace/src/plugins/outbound.js +11 -3
  84. package/packages/dd-trace/src/plugins/tracing.js +8 -4
  85. package/packages/dd-trace/src/plugins/util/test.js +1 -1
  86. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -1
  87. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +1 -1
  88. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +1 -1
  89. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +2 -2
  90. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +1 -1
  91. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +15 -14
  92. package/packages/dd-trace/src/proxy.js +12 -4
  93. package/packages/dd-trace/src/serverless.js +0 -48
  94. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +8 -0
  95. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +8 -0
  96. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  97. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  98. package/packages/dd-trace/src/standalone/product.js +3 -5
@@ -1,12 +1,24 @@
1
1
  'use strict'
2
2
 
3
+ /**
4
+ * @type {Set<string | symbol>}
5
+ */
3
6
  const skipMethods = new Set([
4
7
  'caller',
5
8
  'arguments',
6
9
  'name',
7
10
  'length'
8
11
  ])
12
+ const skipMethodSize = skipMethods.size
9
13
 
14
+ const nonConfigurableModuleExports = new WeakMap()
15
+
16
+ /**
17
+ * Copies properties from the original function to the wrapped function.
18
+ *
19
+ * @param {Function} original - The original function.
20
+ * @param {Function} wrapped - The wrapped function.
21
+ */
10
22
  function copyProperties (original, wrapped) {
11
23
  if (original.constructor !== wrapped.constructor) {
12
24
  const proto = Object.getPrototypeOf(original)
@@ -23,7 +35,7 @@ function copyProperties (original, wrapped) {
23
35
  if (ownKeys.length !== 2) {
24
36
  for (const key of ownKeys) {
25
37
  if (skipMethods.has(key)) continue
26
- const descriptor = Object.getOwnPropertyDescriptor(original, key)
38
+ const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(original, key))
27
39
  if (descriptor.writable && descriptor.enumerable && descriptor.configurable) {
28
40
  wrapped[key] = original[key]
29
41
  } else if (descriptor.writable || descriptor.configurable || !Object.hasOwn(wrapped, key)) {
@@ -33,6 +45,33 @@ function copyProperties (original, wrapped) {
33
45
  }
34
46
  }
35
47
 
48
+ /**
49
+ * Copies properties from the original object to the wrapped object, skipping a specific key.
50
+ *
51
+ * @param {Record<string | symbol, unknown>} original - The original object.
52
+ * @param {Record<string | symbol, unknown>} wrapped - The wrapped object.
53
+ * @param {string | symbol} skipKey - The key to skip during copying.
54
+ */
55
+ function copyObjectProperties (original, wrapped, skipKey) {
56
+ const ownKeys = Reflect.ownKeys(original)
57
+ for (const key of ownKeys) {
58
+ if (key === skipKey) continue
59
+ const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(original, key))
60
+ if (descriptor.writable && descriptor.enumerable && descriptor.configurable) {
61
+ wrapped[key] = original[key]
62
+ } else if (descriptor.writable || descriptor.configurable || !Object.hasOwn(wrapped, key)) {
63
+ Object.defineProperty(wrapped, key, descriptor)
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Wraps a function with a wrapper function.
70
+ *
71
+ * @param {Function} original - The original function to wrap.
72
+ * @param {(original: Function) => Function} wrapper - The wrapper function.
73
+ * @returns {Function} The wrapped function.
74
+ */
36
75
  function wrapFunction (original, wrapper) {
37
76
  const wrapped = wrapper(original)
38
77
 
@@ -44,28 +83,55 @@ function wrapFunction (original, wrapper) {
44
83
  return wrapped
45
84
  }
46
85
 
47
- function wrap (target, name, wrapper) {
48
- assertMethod(target, name)
86
+ /**
87
+ * Wraps a method of an object with a wrapper function.
88
+ *
89
+ * @param {Record<string | symbol, unknown> | Function} target - The target
90
+ * object.
91
+ * @param {string | symbol} name - The property key of the method to wrap.
92
+ * @param {(original: Function) => (...args) => any} wrapper - The wrapper function.
93
+ * @param {{ replaceGetter?: boolean }} [options] - If `replaceGetter` is set to
94
+ * true, the getter is accessed and the getter is replaced with one that just
95
+ * returns the earlier retrieved value. Use with care! This may only be done in
96
+ * case the getter absolutely has no side effect and no setter is defined for the
97
+ * property.
98
+ * @returns {Record<string | symbol, unknown> | Function} The target object with
99
+ * the wrapped method.
100
+ */
101
+ function wrap (target, name, wrapper, options) {
49
102
  if (typeof wrapper !== 'function') {
50
103
  throw new Error(wrapper ? 'Target is not a function' : 'No function provided')
51
104
  }
52
105
 
53
- const original = target[name]
54
- const wrapped = wrapper(original)
106
+ // No descriptor means original was on the prototype. This is not totally
107
+ // safe, since we define the property on the target. That could have an impact
108
+ // in case e.g., the own keys are checks.
109
+ const descriptor = Object.getOwnPropertyDescriptor(target, name) ?? {
110
+ value: target[name],
111
+ writable: true,
112
+ configurable: true,
113
+ enumerable: false
114
+ }
115
+
116
+ if (descriptor.set && (!descriptor.get || options?.replaceGetter)) {
117
+ // It is possible to support these cases by instrumenting both the getter
118
+ // and setter (or only the setter, in case that is a use case).
119
+ // For now, this is not supported due to the complexity and the fact that
120
+ // this is not a common use case.
121
+ throw new Error(options?.replaceGetter
122
+ ? 'Replacing a getter/setter pair is not supported. Implement if required.'
123
+ : 'Replacing setters is not supported. Implement if required.')
124
+ }
55
125
 
56
- if (typeof original === 'function') copyProperties(original, wrapped)
126
+ const original = descriptor.value ?? options?.replaceGetter ? target[name] : descriptor.get
57
127
 
58
- let descriptor = Object.getOwnPropertyDescriptor(target, name)
128
+ assertMethod(target, name, original)
59
129
 
60
- // No descriptor means original was on the prototype
61
- if (descriptor === undefined) {
62
- descriptor = {
63
- value: wrapped,
64
- writable: true,
65
- configurable: true,
66
- enumerable: false
67
- }
68
- } else if (descriptor.writable) {
130
+ const wrapped = wrapper(original)
131
+
132
+ copyProperties(original, wrapped)
133
+
134
+ if (descriptor.writable) {
69
135
  // Fast path for assigned properties.
70
136
  if (descriptor.configurable && descriptor.enumerable) {
71
137
  target[name] = wrapped
@@ -73,25 +139,58 @@ function wrap (target, name, wrapper) {
73
139
  }
74
140
  descriptor.value = wrapped
75
141
  } else {
76
- if (descriptor.get || descriptor.set) {
77
- // TODO(BridgeAR): What happens in case there is a setter? This seems wrong?
78
- // What happens in case the user does indeed set this to a different value?
79
- // In that case the getter would potentially return the wrong value?
80
- descriptor.get = () => wrapped
142
+ if (descriptor.get) {
143
+ // `replaceGetter` may only be used when the getter has no side effect.
144
+ if (options?.replaceGetter) {
145
+ descriptor.get = () => wrapped
146
+ } else {
147
+ descriptor.get = wrapped
148
+ }
81
149
  } else {
82
150
  descriptor.value = wrapped
83
151
  }
84
152
 
85
153
  if (descriptor.configurable === false) {
86
- // TODO(BridgeAR): Bail out instead (throw). It is unclear if the newly
87
- // created object is actually used. If it's not used, the wrapping would
88
- // have had no effect without noticing. It is also unclear what would happen
89
- // in case user code would check for properties to be own properties. That
90
- // would fail with this code. A function being replaced with an object is
91
- // also not possible.
92
- return Object.create(target, {
93
- [name]: descriptor
94
- })
154
+ // TODO(BridgeAR): This currently only works on the most outer part. The
155
+ // moduleExports object.
156
+ //
157
+ // It would be possible to also implement it for non moduleExports objects
158
+ // by passing through the moduleExports object and the property names that
159
+ // are accessed. That way it would be possible to redefine the complete
160
+ // property chain. Example:
161
+ //
162
+ // shimmer.wrap(hapi.Server.prototype, 'start', wrapStart)
163
+ // shimmer.wrap(hapi.Server.prototype, 'ext', wrapExt)
164
+ //
165
+ // shimmer.wrap(hapi, 'Server', 'prototype', 'start', wrapStart)
166
+ // shimmer.wrap(hapi, 'Server', 'prototype', 'ext', wrapExt)
167
+ //
168
+ // That would however still not resolve the issue about the user replacing
169
+ // the return value so that the hook picks up the new hapi moduleExports
170
+ // object. To safely fix that, we would have to couple the register helper
171
+ // with this code. That way it would be possible to directly pass through
172
+ // the entries.
173
+
174
+ // In case more than a single property is not configurable and writable,
175
+ // Just reuse the already created object.
176
+ let moduleExports = nonConfigurableModuleExports.get(target)
177
+ if (!moduleExports) {
178
+ if (typeof target === 'function') {
179
+ const original = target
180
+ moduleExports = function (...args) { return original.apply(original, args) }
181
+ // This is a rare case. Accept the slight performance hit.
182
+ skipMethods.add(name)
183
+ copyProperties(target, moduleExports)
184
+ if (skipMethods.size === skipMethodSize + 1) {
185
+ skipMethods.delete(name)
186
+ }
187
+ } else {
188
+ moduleExports = Object.create(target)
189
+ copyObjectProperties(target, moduleExports, name)
190
+ }
191
+ nonConfigurableModuleExports.set(target, moduleExports)
192
+ }
193
+ target = moduleExports
95
194
  }
96
195
  }
97
196
 
@@ -100,6 +199,16 @@ function wrap (target, name, wrapper) {
100
199
  return target
101
200
  }
102
201
 
202
+ /**
203
+ * Wraps multiple methods and or multiple objects with a wrapper function.
204
+ * May also receive a single method or object or a single method name.
205
+ *
206
+ * @param {Array<Record<string | symbol, unknown> | Function> |
207
+ * Record<string | symbol, unknown> |
208
+ * Function} targets - The target objects.
209
+ * @param {Array<string | symbol> | string | symbol} names - The property keys of the methods to wrap.
210
+ * @param {(original: Function) => (...args) => any} wrapper - The wrapper function.
211
+ */
103
212
  function massWrap (targets, names, wrapper) {
104
213
  targets = toArray(targets)
105
214
  names = toArray(names)
@@ -111,19 +220,35 @@ function massWrap (targets, names, wrapper) {
111
220
  }
112
221
  }
113
222
 
223
+ /**
224
+ * Converts a value to an array if it is not already an array.
225
+ *
226
+ * @template T
227
+ * @param {T | T[]} maybeArray - The value to convert.
228
+ * @returns {T[]} The value as an array.
229
+ */
114
230
  function toArray (maybeArray) {
115
231
  return Array.isArray(maybeArray) ? maybeArray : [maybeArray]
116
232
  }
117
233
 
118
- function assertMethod (target, name) {
119
- if (typeof target?.[name] !== 'function') {
234
+ /**
235
+ * Asserts that a method is a function.
236
+ *
237
+ * @param {Record<string | symbol, unknown> | Function} target - The target object.
238
+ * @param {string | symbol} name - The property key of the method.
239
+ * @param {unknown} method - The method to assert.
240
+ * @throws {Error} If the method is not a function.
241
+ */
242
+ function assertMethod (target, name, method) {
243
+ if (typeof method !== 'function') {
120
244
  let message = 'No target object provided'
121
245
 
122
246
  if (target) {
123
247
  if (typeof target !== 'object' && typeof target !== 'function') {
124
248
  message = 'Invalid target'
125
249
  } else {
126
- message = target[name] ? `Original method ${name} is not a function` : `No original method ${name}`
250
+ name = String(name)
251
+ message = method ? `Original method ${name} is not a function` : `No original method ${name}`
127
252
  }
128
253
  }
129
254
 
@@ -131,6 +256,12 @@ function assertMethod (target, name) {
131
256
  }
132
257
  }
133
258
 
259
+ /**
260
+ * Asserts that a target is not a class constructor.
261
+ *
262
+ * @param {Function} target - The target function.
263
+ * @throws {Error} If the target is a class constructor.
264
+ */
134
265
  function assertNotClass (target) {
135
266
  if (Function.prototype.toString.call(target).startsWith('class')) {
136
267
  throw new Error('Target is a native class constructor and cannot be wrapped.')
@@ -4,10 +4,13 @@ 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')
7
+ const { keepTrace } = require('../priority_sampler')
8
+ const { ASM } = require('../standalone/product')
7
9
 
8
10
  const MAX_SIZE = 4096
9
11
 
10
12
  let enabled
13
+ let asmStandaloneEnabled
11
14
 
12
15
  /**
13
16
  * @type {TTLCache}
@@ -20,11 +23,12 @@ class NoopTTLCache {
20
23
  has (_key) { return false }
21
24
  }
22
25
 
23
- function configure ({ apiSecurity }) {
24
- enabled = apiSecurity.enabled
25
- sampledRequests = apiSecurity.sampleDelay === 0
26
+ function configure ({ appsec, apmTracingEnabled }) {
27
+ enabled = appsec.apiSecurity.enabled
28
+ asmStandaloneEnabled = apmTracingEnabled === false
29
+ sampledRequests = appsec.apiSecurity.sampleDelay === 0
26
30
  ? new NoopTTLCache()
27
- : new TTLCache({ max: MAX_SIZE, ttl: apiSecurity.sampleDelay * 1000 })
31
+ : new TTLCache({ max: MAX_SIZE, ttl: appsec.apiSecurity.sampleDelay * 1000 })
28
32
  }
29
33
 
30
34
  function disable () {
@@ -41,14 +45,18 @@ function sampleRequest (req, res, force = false) {
41
45
  const rootSpan = web.root(req)
42
46
  if (!rootSpan) return false
43
47
 
44
- let priority = getSpanPriority(rootSpan)
45
- if (!priority) {
46
- rootSpan._prioritySampler?.sample(rootSpan)
47
- priority = getSpanPriority(rootSpan)
48
- }
49
-
50
- if (priority === AUTO_REJECT || priority === USER_REJECT) {
51
- return false
48
+ if (asmStandaloneEnabled) {
49
+ keepTrace(rootSpan, ASM)
50
+ } else {
51
+ let priority = getSpanPriority(rootSpan)
52
+ if (!priority) {
53
+ rootSpan._prioritySampler?.sample(rootSpan)
54
+ priority = getSpanPriority(rootSpan)
55
+ }
56
+
57
+ if (priority === AUTO_REJECT || priority === USER_REJECT) {
58
+ return false
59
+ }
52
60
  }
53
61
 
54
62
  if (force) {
@@ -38,8 +38,8 @@ function onGraphqlStartResolve ({ context, resolverInfo }) {
38
38
 
39
39
  if (!resolverInfo || typeof resolverInfo !== 'object') return
40
40
 
41
- const actions = waf.run({ ephemeral: { [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo } }, req)
42
- const blockingAction = getBlockingAction(actions)
41
+ const result = waf.run({ ephemeral: { [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo } }, req)
42
+ const blockingAction = getBlockingAction(result?.actions)
43
43
  if (blockingAction) {
44
44
  const requestData = graphqlRequestData.get(req)
45
45
  if (requestData?.isInGraphqlRequest) {
@@ -19,9 +19,11 @@ let config
19
19
  const hardcodedSecretCh = dc.channel('datadog:secrets:result')
20
20
  let rewriter
21
21
  let unwrapCompile = () => {}
22
- let getPrepareStackTrace, cacheRewrittenSourceMap
22
+ let getPrepareStackTrace
23
+ let cacheRewrittenSourceMap
23
24
  let kSymbolPrepareStackTrace
24
- let esmRewriterEnabled = false
25
+
26
+ function noop () {}
25
27
 
26
28
  function isFlagPresent (flag) {
27
29
  return process.env.NODE_OPTIONS?.includes(flag) ||
@@ -155,7 +157,7 @@ function shimPrepareStackTrace () {
155
157
  return
156
158
  }
157
159
  const pstDescriptor = Object.getOwnPropertyDescriptor(global.Error, 'prepareStackTrace')
158
- if (!pstDescriptor || pstDescriptor.configurable) {
160
+ if (pstDescriptor?.configurable || pstDescriptor?.writable) {
159
161
  Object.defineProperty(global.Error, 'prepareStackTrace', getPrepareStackTraceAccessor())
160
162
  }
161
163
  shimmedPrepareStackTrace = true
@@ -181,16 +183,17 @@ function isEsmConfigured () {
181
183
  const hasLoaderArg = isFlagPresent('--loader') || isFlagPresent('--experimental-loader')
182
184
  if (hasLoaderArg) return true
183
185
 
184
- const initializeLoaded = Object.keys(require.cache).find(file => file.includes('import-in-the-middle/hook.js'))
185
- return !!initializeLoaded
186
+ // Fast path for common case when enabled
187
+ if (require.cache[`${process.cwd()}/node_modules/import-in-the-middle/hook.js`]) {
188
+ return true
189
+ }
190
+ return Object.keys(require.cache).some(file => file.endsWith('import-in-the-middle/hook.js'))
186
191
  }
187
192
 
188
- function enableEsmRewriter (telemetryVerbosity) {
189
- if (isMainThread && Module.register && !esmRewriterEnabled && isEsmConfigured()) {
193
+ let enableEsmRewriter = function (telemetryVerbosity) {
194
+ if (isMainThread && Module.register && isEsmConfigured()) {
190
195
  shimPrepareStackTrace()
191
196
 
192
- esmRewriterEnabled = true
193
-
194
197
  const { port1, port2 } = new MessageChannel()
195
198
 
196
199
  port1.on('message', (message) => {
@@ -229,6 +232,8 @@ function enableEsmRewriter (telemetryVerbosity) {
229
232
  }
230
233
 
231
234
  cacheRewrittenSourceMap = require('@datadog/wasm-js-rewriter/js/source-map').cacheRewrittenSourceMap
235
+
236
+ enableEsmRewriter = noop
232
237
  }
233
238
  }
234
239
 
@@ -34,6 +34,7 @@ const UserTracking = require('./user_tracking')
34
34
  const { storage } = require('../../../datadog-core')
35
35
  const graphql = require('./graphql')
36
36
  const rasp = require('./rasp')
37
+ const { isInServerlessEnvironment } = require('../serverless')
37
38
 
38
39
  const responseAnalyzedSet = new WeakSet()
39
40
 
@@ -59,7 +60,7 @@ function enable (_config) {
59
60
 
60
61
  Reporter.setRateLimit(_config.appsec.rateLimit)
61
62
 
62
- apiSecuritySampler.configure(_config.appsec)
63
+ apiSecuritySampler.configure(_config)
63
64
 
64
65
  UserTracking.setCollectionMode(_config.appsec.eventTracking.mode, false)
65
66
 
@@ -83,7 +84,9 @@ function enable (_config) {
83
84
  isEnabled = true
84
85
  config = _config
85
86
  } catch (err) {
86
- log.error('[ASM] Unable to start AppSec', err)
87
+ if (!isInServerlessEnvironment()) {
88
+ log.error('[ASM] Unable to start AppSec', err)
89
+ }
87
90
 
88
91
  disable()
89
92
  }
@@ -106,7 +109,7 @@ function onRequestBodyParsed ({ req, res, body, abortController }) {
106
109
  }
107
110
  }, req)
108
111
 
109
- handleResults(results, req, res, rootSpan, abortController)
112
+ handleResults(results?.actions, req, res, rootSpan, abortController)
110
113
  }
111
114
 
112
115
  function onRequestCookieParser ({ req, res, abortController, cookies }) {
@@ -121,7 +124,7 @@ function onRequestCookieParser ({ req, res, abortController, cookies }) {
121
124
  }
122
125
  }, req)
123
126
 
124
- handleResults(results, req, res, rootSpan, abortController)
127
+ handleResults(results?.actions, req, res, rootSpan, abortController)
125
128
  }
126
129
 
127
130
  function incomingHttpStartTranslator ({ req, res, abortController }) {
@@ -149,9 +152,9 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
149
152
  persistent[addresses.HTTP_CLIENT_IP] = clientIp
150
153
  }
151
154
 
152
- const actions = waf.run({ persistent }, req)
155
+ const results = waf.run({ persistent }, req)
153
156
 
154
- handleResults(actions, req, res, rootSpan, abortController)
157
+ handleResults(results?.actions, req, res, rootSpan, abortController)
155
158
  }
156
159
 
157
160
  function incomingHttpEndTranslator ({ req, res }) {
@@ -198,7 +201,7 @@ function onPassportVerify ({ framework, login, user, success, abortController })
198
201
 
199
202
  const results = UserTracking.trackLogin(framework, login, user, success, rootSpan)
200
203
 
201
- handleResults(results, store.req, store.req.res, rootSpan, abortController)
204
+ handleResults(results?.actions, store.req, store.req.res, rootSpan, abortController)
202
205
  }
203
206
 
204
207
  function onPassportDeserializeUser ({ user, abortController }) {
@@ -212,7 +215,7 @@ function onPassportDeserializeUser ({ user, abortController }) {
212
215
 
213
216
  const results = UserTracking.trackUser(user, rootSpan)
214
217
 
215
- handleResults(results, store.req, store.req.res, rootSpan, abortController)
218
+ handleResults(results?.actions, store.req, store.req.res, rootSpan, abortController)
216
219
  }
217
220
 
218
221
  function onExpressSession ({ req, res, sessionId, abortController }) {
@@ -231,7 +234,7 @@ function onExpressSession ({ req, res, sessionId, abortController }) {
231
234
  }
232
235
  }, req)
233
236
 
234
- handleResults(results, req, res, rootSpan, abortController)
237
+ handleResults(results?.actions, req, res, rootSpan, abortController)
235
238
  }
236
239
 
237
240
  function onRequestQueryParsed ({ req, res, query, abortController }) {
@@ -251,7 +254,7 @@ function onRequestQueryParsed ({ req, res, query, abortController }) {
251
254
  }
252
255
  }, req)
253
256
 
254
- handleResults(results, req, res, rootSpan, abortController)
257
+ handleResults(results?.actions, req, res, rootSpan, abortController)
255
258
  }
256
259
 
257
260
  function onRequestProcessParams ({ req, res, abortController, params }) {
@@ -266,7 +269,7 @@ function onRequestProcessParams ({ req, res, abortController, params }) {
266
269
  }
267
270
  }, req)
268
271
 
269
- handleResults(results, req, res, rootSpan, abortController)
272
+ handleResults(results?.actions, req, res, rootSpan, abortController)
270
273
  }
271
274
 
272
275
  function onResponseBody ({ req, res, body }) {
@@ -308,7 +311,7 @@ function onResponseWriteHead ({ req, res, abortController, statusCode, responseH
308
311
 
309
312
  responseAnalyzedSet.add(res)
310
313
 
311
- handleResults(results, req, res, rootSpan, abortController)
314
+ handleResults(results?.actions, req, res, rootSpan, abortController)
312
315
  }
313
316
 
314
317
  function onResponseSetHeader ({ res, abortController }) {
@@ -85,10 +85,12 @@ function blockOnDatadogRaspAbortError ({ error }) {
85
85
  const abortError = findDatadogRaspAbortError(error)
86
86
  if (!abortError) return false
87
87
 
88
- const { req, res, blockingAction, raspRule } = abortError
88
+ const { req, res, blockingAction, raspRule, ruleTriggered } = abortError
89
89
  if (!isBlocked(res)) {
90
90
  const blocked = block(req, res, web.root(req), null, blockingAction)
91
- updateRaspRuleMatchMetricTags(req, raspRule, true, blocked)
91
+ if (ruleTriggered) {
92
+ updateRaspRuleMatchMetricTags(req, raspRule, true, blocked)
93
+ }
92
94
  }
93
95
 
94
96
  return true
@@ -20,23 +20,26 @@ const RULE_TYPES = {
20
20
  }
21
21
 
22
22
  class DatadogRaspAbortError extends Error {
23
- constructor (req, res, blockingAction, raspRule) {
23
+ constructor (req, res, blockingAction, raspRule, ruleTriggered) {
24
24
  super('DatadogRaspAbortError')
25
25
  this.name = 'DatadogRaspAbortError'
26
26
  this.req = req
27
27
  this.res = res
28
28
  this.blockingAction = blockingAction
29
29
  this.raspRule = raspRule
30
+ this.ruleTriggered = ruleTriggered
30
31
  }
31
32
  }
32
33
 
33
- function handleResult (actions, req, res, abortController, config, raspRule) {
34
- const generateStackTraceAction = actions?.generate_stack
34
+ function handleResult (result, req, res, abortController, config, raspRule) {
35
+ const generateStackTraceAction = result?.actions?.generate_stack
35
36
 
36
37
  const { enabled, maxDepth, maxStackTraces } = config.appsec.stackTrace
37
38
 
38
39
  const rootSpan = web.root(req)
39
40
 
41
+ const ruleTriggered = !!result?.events?.length
42
+
40
43
  if (generateStackTraceAction && enabled && canReportStackTrace(rootSpan, maxStackTraces)) {
41
44
  const frames = getCallsiteFrames(maxDepth)
42
45
 
@@ -48,11 +51,11 @@ function handleResult (actions, req, res, abortController, config, raspRule) {
48
51
  }
49
52
 
50
53
  if (abortController && !abortOnUncaughtException) {
51
- const blockingAction = getBlockingAction(actions)
54
+ const blockingAction = getBlockingAction(result?.actions)
52
55
 
53
56
  // Should block only in express
54
57
  if (blockingAction && rootSpan?.context()._name === 'express.request') {
55
- const abortError = new DatadogRaspAbortError(req, res, blockingAction, raspRule)
58
+ const abortError = new DatadogRaspAbortError(req, res, blockingAction, raspRule, ruleTriggered)
56
59
  abortController.abort(abortError)
57
60
 
58
61
  // TODO Delete this when support for node 16 is removed
@@ -64,7 +67,9 @@ function handleResult (actions, req, res, abortController, config, raspRule) {
64
67
  }
65
68
  }
66
69
 
67
- updateRaspRuleMatchMetricTags(req, raspRule, false, false)
70
+ if (ruleTriggered) {
71
+ updateRaspRuleMatchMetricTags(req, raspRule, false, false)
72
+ }
68
73
  }
69
74
 
70
75
  module.exports = {
@@ -9,8 +9,8 @@ const { setUserTags } = require('./set_user')
9
9
  const log = require('../../log')
10
10
 
11
11
  function isUserBlocked (user) {
12
- const actions = waf.run({ persistent: { [USER_ID]: user.id } })
13
- return !!getBlockingAction(actions)
12
+ const results = waf.run({ persistent: { [USER_ID]: user.id } })
13
+ return !!getBlockingAction(results?.actions)
14
14
  }
15
15
 
16
16
  function checkUserAndSetUser (tracer, user) {
@@ -41,8 +41,7 @@ function newStore () {
41
41
  wafErrorCode: null,
42
42
  raspErrorCode: null,
43
43
  wafVersion: null,
44
- rulesVersion: null,
45
- ruleTriggered: null
44
+ rulesVersion: null
46
45
  }
47
46
  }
48
47
  }
@@ -49,10 +49,6 @@ function trackRaspMetrics (store, metrics, raspRule) {
49
49
  telemetryMetrics.rulesVersion = metrics.rulesVersion
50
50
  }
51
51
 
52
- if (metrics.ruleTriggered) {
53
- telemetryMetrics.ruleTriggered = true
54
- }
55
-
56
52
  appsecMetrics.count('rasp.rule.eval', tags).inc(1)
57
53
 
58
54
  if (metrics.errorCode) {
@@ -68,7 +64,6 @@ function trackRaspMetrics (store, metrics, raspRule) {
68
64
 
69
65
  function trackRaspRuleMatch (store, raspRule, blockTriggered, blocked) {
70
66
  const telemetryMetrics = store[DD_TELEMETRY_REQUEST_METRICS]
71
- if (!telemetryMetrics.ruleTriggered) return
72
67
 
73
68
  const tags = {
74
69
  waf_version: telemetryMetrics.wafVersion,
@@ -82,10 +77,6 @@ function trackRaspRuleMatch (store, raspRule, blockTriggered, blocked) {
82
77
  }
83
78
 
84
79
  appsecMetrics.count('rasp.rule.match', tags).inc(1)
85
-
86
- // this is needed to not count it twice for the same match
87
- // but it also means it can only be called once per waf call even if there are multiple rasp match
88
- telemetryMetrics.ruleTriggered = null
89
80
  }
90
81
 
91
82
  function trackRaspRuleSkipped (raspRule, reason) {
@@ -19,7 +19,7 @@ class WAFContextWrapper {
19
19
  this.rulesVersion = rulesVersion
20
20
  this.knownAddresses = knownAddresses
21
21
  this.addressesToSkip = new Set()
22
- this.cachedUserIdActions = new Map()
22
+ this.cachedUserIdResults = new Map()
23
23
  }
24
24
 
25
25
  run ({ persistent, ephemeral }, raspRule) {
@@ -36,9 +36,9 @@ class WAFContextWrapper {
36
36
  // TODO: make this universal
37
37
  const userId = persistent?.[addresses.USER_ID] || ephemeral?.[addresses.USER_ID]
38
38
  if (userId) {
39
- const cachedAction = this.cachedUserIdActions.get(userId)
40
- if (cachedAction) {
41
- return cachedAction
39
+ const cachedResults = this.cachedUserIdResults.get(userId)
40
+ if (cachedResults) {
41
+ return cachedResults
42
42
  }
43
43
  }
44
44
 
@@ -142,7 +142,7 @@ class WAFContextWrapper {
142
142
 
143
143
  Reporter.reportDerivatives(result.derivatives)
144
144
 
145
- return result.actions
145
+ return result
146
146
  } catch (err) {
147
147
  log.error('[ASM] Error while running the AppSec WAF', err)
148
148
 
@@ -168,7 +168,7 @@ class WAFContextWrapper {
168
168
  const parameter = match.parameters[k]
169
169
 
170
170
  if (parameter?.address === addresses.USER_ID) {
171
- this.cachedUserIdActions.set(userId, result.actions)
171
+ this.cachedUserIdResults.set(userId, result)
172
172
  return
173
173
  }
174
174
  }