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.
- package/README.md +5 -0
- package/index.d.ts +54 -6
- package/package.json +1 -1
- package/packages/datadog-instrumentations/src/amqplib.js +8 -5
- package/packages/datadog-instrumentations/src/child_process.js +2 -1
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +16 -1
- package/packages/datadog-instrumentations/src/couchbase.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +41 -46
- package/packages/datadog-instrumentations/src/express.js +2 -6
- package/packages/datadog-instrumentations/src/fs.js +6 -5
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +17 -12
- package/packages/datadog-instrumentations/src/http/client.js +2 -1
- package/packages/datadog-instrumentations/src/iovalkey.js +51 -0
- package/packages/datadog-instrumentations/src/jest.js +49 -41
- package/packages/datadog-instrumentations/src/kafkajs.js +21 -8
- package/packages/datadog-instrumentations/src/mocha/main.js +33 -46
- package/packages/datadog-instrumentations/src/mocha/utils.js +72 -75
- package/packages/datadog-instrumentations/src/mysql2.js +3 -1
- package/packages/datadog-instrumentations/src/net.js +3 -1
- package/packages/datadog-instrumentations/src/next.js +6 -14
- package/packages/datadog-instrumentations/src/pg.js +5 -11
- package/packages/datadog-instrumentations/src/playwright.js +60 -69
- package/packages/datadog-instrumentations/src/url.js +9 -17
- package/packages/datadog-instrumentations/src/vitest.js +55 -75
- package/packages/datadog-plugin-cucumber/src/index.js +29 -18
- package/packages/datadog-plugin-iovalkey/src/index.js +18 -0
- package/packages/datadog-plugin-jest/src/index.js +14 -8
- package/packages/datadog-plugin-kafkajs/src/producer.js +8 -5
- package/packages/datadog-plugin-mocha/src/index.js +55 -35
- package/packages/datadog-plugin-playwright/src/index.js +26 -20
- package/packages/datadog-plugin-redis/src/index.js +8 -3
- package/packages/datadog-plugin-vitest/src/index.js +53 -42
- package/packages/datadog-shimmer/src/shimmer.js +164 -33
- package/packages/dd-trace/src/appsec/graphql.js +2 -2
- package/packages/dd-trace/src/appsec/index.js +14 -11
- package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/utils.js +11 -6
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
- package/packages/dd-trace/src/appsec/telemetry/index.js +1 -2
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -9
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -6
- package/packages/dd-trace/src/config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +59 -7
- package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -26
- package/packages/dd-trace/src/debugger/devtools_client/send.js +8 -7
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +15 -7
- package/packages/dd-trace/src/debugger/devtools_client/state.js +21 -1
- package/packages/dd-trace/src/dogstatsd.js +2 -0
- package/packages/dd-trace/src/llmobs/tagger.js +3 -3
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/proxy.js +0 -4
- package/packages/dd-trace/src/serverless.js +0 -48
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +8 -0
- 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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
142
|
+
testSuiteSpan.setTag('error', error)
|
|
143
|
+
testSuiteSpan.setTag(TEST_STATUS, 'fail')
|
|
141
144
|
} else {
|
|
142
|
-
|
|
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
|
-
|
|
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.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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:
|
|
24
|
+
type: this._spanType,
|
|
20
25
|
meta: {
|
|
21
|
-
'db.type':
|
|
26
|
+
'db.type': this._spanType,
|
|
22
27
|
'db.name': db || '0',
|
|
23
|
-
|
|
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.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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.
|
|
159
|
-
const
|
|
160
|
-
const 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
|
-
|
|
178
|
-
|
|
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.
|
|
256
|
-
testSuiteAbsolutePath,
|
|
257
|
-
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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.
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
126
|
+
const original = descriptor.value ?? options?.replaceGetter ? target[name] : descriptor.get
|
|
57
127
|
|
|
58
|
-
|
|
128
|
+
assertMethod(target, name, original)
|
|
59
129
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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):
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 }) {
|