dd-trace 5.52.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 (55) hide show
  1. package/README.md +5 -0
  2. package/index.d.ts +54 -6
  3. package/package.json +1 -1
  4. package/packages/datadog-instrumentations/src/amqplib.js +8 -5
  5. package/packages/datadog-instrumentations/src/child_process.js +2 -1
  6. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +16 -1
  7. package/packages/datadog-instrumentations/src/couchbase.js +2 -1
  8. package/packages/datadog-instrumentations/src/cucumber.js +41 -46
  9. package/packages/datadog-instrumentations/src/express.js +2 -6
  10. package/packages/datadog-instrumentations/src/fs.js +6 -5
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  12. package/packages/datadog-instrumentations/src/helpers/register.js +17 -12
  13. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  14. package/packages/datadog-instrumentations/src/iovalkey.js +51 -0
  15. package/packages/datadog-instrumentations/src/jest.js +49 -41
  16. package/packages/datadog-instrumentations/src/kafkajs.js +21 -8
  17. package/packages/datadog-instrumentations/src/mocha/main.js +33 -46
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +72 -75
  19. package/packages/datadog-instrumentations/src/mysql2.js +3 -1
  20. package/packages/datadog-instrumentations/src/net.js +3 -1
  21. package/packages/datadog-instrumentations/src/next.js +6 -14
  22. package/packages/datadog-instrumentations/src/pg.js +5 -11
  23. package/packages/datadog-instrumentations/src/playwright.js +60 -69
  24. package/packages/datadog-instrumentations/src/url.js +9 -17
  25. package/packages/datadog-instrumentations/src/vitest.js +55 -75
  26. package/packages/datadog-plugin-cucumber/src/index.js +29 -18
  27. package/packages/datadog-plugin-iovalkey/src/index.js +18 -0
  28. package/packages/datadog-plugin-jest/src/index.js +14 -8
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +8 -5
  30. package/packages/datadog-plugin-mocha/src/index.js +55 -35
  31. package/packages/datadog-plugin-playwright/src/index.js +26 -20
  32. package/packages/datadog-plugin-redis/src/index.js +8 -3
  33. package/packages/datadog-plugin-vitest/src/index.js +53 -42
  34. package/packages/datadog-shimmer/src/shimmer.js +164 -33
  35. package/packages/dd-trace/src/appsec/graphql.js +2 -2
  36. package/packages/dd-trace/src/appsec/index.js +14 -11
  37. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  38. package/packages/dd-trace/src/appsec/rasp/utils.js +11 -6
  39. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  40. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -2
  41. package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -9
  42. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -6
  43. package/packages/dd-trace/src/config.js +1 -1
  44. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +59 -7
  45. package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -26
  46. package/packages/dd-trace/src/debugger/devtools_client/send.js +8 -7
  47. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +15 -7
  48. package/packages/dd-trace/src/debugger/devtools_client/state.js +21 -1
  49. package/packages/dd-trace/src/dogstatsd.js +2 -0
  50. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  51. package/packages/dd-trace/src/plugins/index.js +1 -0
  52. package/packages/dd-trace/src/proxy.js +0 -4
  53. package/packages/dd-trace/src/serverless.js +0 -48
  54. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +8 -0
  55. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
@@ -97,7 +97,9 @@ class PlaywrightPlugin extends CiPlugin {
97
97
  this.numFailedTests = 0
98
98
  })
99
99
 
100
- this.addSub('ci:playwright:test-suite:start', (testSuiteAbsolutePath) => {
100
+ this.addBind('ci:playwright:test-suite:start', (ctx) => {
101
+ const { testSuiteAbsolutePath } = ctx
102
+
101
103
  const store = storage('legacy').getStore()
102
104
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
103
105
  const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
@@ -126,27 +128,28 @@ class PlaywrightPlugin extends CiPlugin {
126
128
  }
127
129
  })
128
130
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
129
- this.enter(testSuiteSpan, store)
131
+ ctx.parentStore = store
132
+ ctx.currentStore = { ...store, testSuiteSpan }
130
133
 
131
134
  this._testSuites.set(testSuiteAbsolutePath, testSuiteSpan)
135
+
136
+ return ctx.currentStore
132
137
  })
133
138
 
134
- this.addSub('ci:playwright:test-suite:finish', ({ status, error }) => {
135
- const store = storage('legacy').getStore()
136
- const span = store && store.span
137
- if (!span) return
139
+ this.addSub('ci:playwright:test-suite:finish', ({ testSuiteSpan, status, error }) => {
140
+ if (!testSuiteSpan) return
138
141
  if (error) {
139
- span.setTag('error', error)
140
- span.setTag(TEST_STATUS, 'fail')
142
+ testSuiteSpan.setTag('error', error)
143
+ testSuiteSpan.setTag(TEST_STATUS, 'fail')
141
144
  } else {
142
- span.setTag(TEST_STATUS, status)
145
+ testSuiteSpan.setTag(TEST_STATUS, status)
143
146
  }
144
147
 
145
148
  if (status === 'fail' || error) {
146
149
  this.numFailedSuites++
147
150
  }
148
151
 
149
- span.finish()
152
+ testSuiteSpan.finish()
150
153
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
151
154
  })
152
155
 
@@ -180,13 +183,14 @@ class PlaywrightPlugin extends CiPlugin {
180
183
  }
181
184
  })
182
185
 
183
- this.addSub('ci:playwright:test:start', ({
184
- testName,
185
- testSuiteAbsolutePath,
186
- testSourceLine,
187
- browserName,
188
- isDisabled
189
- }) => {
186
+ this.addBind('ci:playwright:test:start', (ctx) => {
187
+ const {
188
+ testName,
189
+ testSuiteAbsolutePath,
190
+ testSourceLine,
191
+ browserName,
192
+ isDisabled
193
+ } = ctx
190
194
  const store = storage('legacy').getStore()
191
195
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
192
196
  const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
@@ -203,7 +207,10 @@ class PlaywrightPlugin extends CiPlugin {
203
207
  span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
204
208
  }
205
209
 
206
- this.enter(span, store)
210
+ ctx.parentStore = store
211
+ ctx.currentStore = { ...store, span }
212
+
213
+ return ctx.currentStore
207
214
  })
208
215
 
209
216
  this.addSub('ci:playwright:worker:report', (serializedTraces) => {
@@ -254,6 +261,7 @@ class PlaywrightPlugin extends CiPlugin {
254
261
  })
255
262
 
256
263
  this.addSub('ci:playwright:test:finish', ({
264
+ span,
257
265
  testStatus,
258
266
  steps,
259
267
  error,
@@ -271,8 +279,6 @@ class PlaywrightPlugin extends CiPlugin {
271
279
  isAtrRetry,
272
280
  onDone
273
281
  }) => {
274
- const store = storage('legacy').getStore()
275
- const span = store && store.span
276
282
  if (!span) return
277
283
 
278
284
  const isRUMActive = span.context()._tags[TEST_IS_RUM_ACTIVE]
@@ -8,6 +8,11 @@ class RedisPlugin extends CachePlugin {
8
8
  static get id () { return 'redis' }
9
9
  static get system () { return 'redis' }
10
10
 
11
+ constructor (...args) {
12
+ super(...args)
13
+ this._spanType = 'redis'
14
+ }
15
+
11
16
  start ({ db, command, args, connectionOptions = {}, connectionName }) {
12
17
  const resource = command
13
18
  const normalizedCommand = command.toUpperCase()
@@ -16,11 +21,11 @@ class RedisPlugin extends CachePlugin {
16
21
  this.startSpan({
17
22
  resource,
18
23
  service: this.serviceName({ pluginConfig: this.config, system: this.system, connectionName }),
19
- type: 'redis',
24
+ type: this._spanType,
20
25
  meta: {
21
- 'db.type': 'redis',
26
+ 'db.type': this._spanType,
22
27
  'db.name': db || '0',
23
- 'redis.raw_command': formatCommand(normalizedCommand, args),
28
+ [`${this._spanType}.raw_command`]: formatCommand(normalizedCommand, args),
24
29
  'out.host': connectionOptions.host,
25
30
  [CLIENT_PORT_KEY]: connectionOptions.port
26
31
  }
@@ -95,19 +95,21 @@ class VitestPlugin extends CiPlugin {
95
95
  onDone(isFaulty)
96
96
  })
97
97
 
98
- this.addSub('ci:vitest:test:start', ({
99
- testName,
100
- testSuiteAbsolutePath,
101
- isRetry,
102
- isNew,
103
- isAttemptToFix,
104
- isQuarantined,
105
- isDisabled,
106
- mightHitProbe,
107
- isRetryReasonEfd,
108
- isRetryReasonAttemptToFix,
109
- isRetryReasonAtr
110
- }) => {
98
+ this.addBind('ci:vitest:test:start', (ctx) => {
99
+ const {
100
+ testName,
101
+ testSuiteAbsolutePath,
102
+ isRetry,
103
+ isNew,
104
+ isAttemptToFix,
105
+ isQuarantined,
106
+ isDisabled,
107
+ mightHitProbe,
108
+ isRetryReasonEfd,
109
+ isRetryReasonAttemptToFix,
110
+ isRetryReasonAtr
111
+ } = ctx
112
+
111
113
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
112
114
  const store = storage('legacy').getStore()
113
115
 
@@ -146,18 +148,21 @@ class VitestPlugin extends CiPlugin {
146
148
  extraTags
147
149
  )
148
150
 
149
- this.enter(span, store)
151
+ ctx.parentStore = store
152
+ ctx.currentStore = { ...store, span }
150
153
 
151
154
  // TODO: there might be multiple tests for which mightHitProbe is true, so activeTestSpan
152
155
  // might be wrongly overwritten.
153
156
  if (mightHitProbe) {
154
157
  this.activeTestSpan = span
155
158
  }
159
+
160
+ return ctx.currentStore
156
161
  })
157
162
 
158
- this.addSub('ci:vitest:test:finish-time', ({ status, task, attemptToFixPassed, attemptToFixFailed }) => {
159
- const store = storage('legacy').getStore()
160
- const span = store?.span
163
+ this.addBind('ci:vitest:test:finish-time', (ctx) => {
164
+ const { status, task, attemptToFixPassed, attemptToFixFailed } = ctx
165
+ const span = ctx.currentStore?.span
161
166
 
162
167
  // we store the finish time to finish at a later hook
163
168
  // this is because the test might fail at a `afterEach` hook
@@ -171,13 +176,15 @@ class VitestPlugin extends CiPlugin {
171
176
  }
172
177
 
173
178
  this.taskToFinishTime.set(task, span._getTime())
179
+
180
+ ctx.parentStore = ctx.currentStore
181
+ ctx.currentStore = { ...ctx.currentStore, span }
174
182
  }
175
- })
176
183
 
177
- this.addSub('ci:vitest:test:pass', ({ task }) => {
178
- const store = storage('legacy').getStore()
179
- const span = store?.span
184
+ return ctx.currentStore
185
+ })
180
186
 
187
+ this.addSub('ci:vitest:test:pass', ({ span, task }) => {
181
188
  if (span) {
182
189
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
183
190
  hasCodeowners: !!span.context()._tags[TEST_CODE_OWNERS]
@@ -189,6 +196,7 @@ class VitestPlugin extends CiPlugin {
189
196
  })
190
197
 
191
198
  this.addSub('ci:vitest:test:error', ({
199
+ span,
192
200
  duration,
193
201
  error,
194
202
  shouldSetProbe,
@@ -196,9 +204,6 @@ class VitestPlugin extends CiPlugin {
196
204
  hasFailedAllRetries,
197
205
  attemptToFixFailed
198
206
  }) => {
199
- const store = storage('legacy').getStore()
200
- const span = store?.span
201
-
202
207
  if (span) {
203
208
  if (shouldSetProbe && this.di) {
204
209
  const probeInformation = this.addDiProbe(error)
@@ -252,10 +257,9 @@ class VitestPlugin extends CiPlugin {
252
257
  testSpan.finish()
253
258
  })
254
259
 
255
- this.addSub('ci:vitest:test-suite:start', ({
256
- testSuiteAbsolutePath,
257
- frameworkVersion
258
- }) => {
260
+ this.addBind('ci:vitest:test-suite:start', (ctx) => {
261
+ const { testSuiteAbsolutePath, frameworkVersion } = ctx
262
+
259
263
  this.command = process.env.DD_CIVISIBILITY_TEST_COMMAND
260
264
  this.frameworkVersion = frameworkVersion
261
265
  const testSessionSpanContext = this.tracer.extract('text_map', {
@@ -305,17 +309,18 @@ class VitestPlugin extends CiPlugin {
305
309
  })
306
310
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
307
311
  const store = storage('legacy').getStore()
308
- this.enter(testSuiteSpan, store)
312
+ ctx.parentStore = store
313
+ ctx.currentStore = { ...store, testSuiteSpan }
309
314
  this.testSuiteSpan = testSuiteSpan
315
+
316
+ return ctx.currentStore
310
317
  })
311
318
 
312
- this.addSub('ci:vitest:test-suite:finish', ({ status, onFinish }) => {
313
- const store = storage('legacy').getStore()
314
- const span = store?.span
315
- if (span) {
316
- span.setTag(TEST_STATUS, status)
317
- span.finish()
318
- finishAllTraceSpans(span)
319
+ this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, onFinish }) => {
320
+ if (testSuiteSpan) {
321
+ testSuiteSpan.setTag(TEST_STATUS, status)
322
+ testSuiteSpan.finish()
323
+ finishAllTraceSpans(testSuiteSpan)
319
324
  }
320
325
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
321
326
  // TODO: too frequent flush - find for method in worker to decrease frequency
@@ -325,13 +330,19 @@ class VitestPlugin extends CiPlugin {
325
330
  }
326
331
  })
327
332
 
328
- this.addSub('ci:vitest:test-suite:error', ({ error }) => {
329
- const store = storage('legacy').getStore()
330
- const span = store?.span
331
- if (span && error) {
332
- span.setTag('error', error)
333
- span.setTag(TEST_STATUS, 'fail')
333
+ this.addBind('ci:vitest:test-suite:error', (ctx) => {
334
+ const { error } = ctx
335
+ const testSuiteSpan = ctx.currentStore?.testSuiteSpan
336
+
337
+ if (testSuiteSpan && error) {
338
+ testSuiteSpan.setTag('error', error)
339
+ testSuiteSpan.setTag(TEST_STATUS, 'fail')
340
+
341
+ ctx.parentStore = ctx.currentStore
342
+ ctx.currentStore = { ...ctx.currentStore, testSuiteSpan }
334
343
  }
344
+
345
+ return ctx.currentStore
335
346
  })
336
347
 
337
348
  this.addSub('ci:vitest:session:finish', ({
@@ -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.')
@@ -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) {
@@ -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
 
@@ -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 }) {