dd-trace 4.17.0 → 4.18.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/index.d.ts +9 -1
- package/package.json +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +5 -0
- package/packages/datadog-instrumentations/src/jest.js +39 -10
- package/packages/datadog-instrumentations/src/knex.js +24 -17
- package/packages/datadog-instrumentations/src/mocha.js +16 -1
- package/packages/datadog-instrumentations/src/next.js +58 -23
- package/packages/datadog-instrumentations/src/playwright.js +11 -6
- package/packages/datadog-plugin-http/src/client.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +11 -3
- package/packages/datadog-plugin-mocha/src/index.js +7 -1
- package/packages/datadog-plugin-next/src/index.js +4 -3
- package/packages/datadog-plugin-playwright/src/index.js +4 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +9 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
- package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
- package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +19 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +2 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
- package/packages/dd-trace/src/config.js +29 -13
- package/packages/dd-trace/src/git_properties.js +16 -15
- package/packages/dd-trace/src/plugins/util/test.js +29 -1
- package/packages/dd-trace/src/profiling/config.js +3 -17
- package/packages/dd-trace/src/profiling/profilers/wall.js +44 -39
- package/packages/dd-trace/src/telemetry/index.js +4 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
- package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
- package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
package/index.d.ts
CHANGED
|
@@ -453,7 +453,15 @@ export declare interface TracerOptions {
|
|
|
453
453
|
* Whether to enable vulnerability redaction
|
|
454
454
|
* @default true
|
|
455
455
|
*/
|
|
456
|
-
redactionEnabled?: boolean
|
|
456
|
+
redactionEnabled?: boolean,
|
|
457
|
+
/**
|
|
458
|
+
* Specifies a regex that will redact sensitive source names in vulnerability reports.
|
|
459
|
+
*/
|
|
460
|
+
redactionNamePattern?: string,
|
|
461
|
+
/**
|
|
462
|
+
* Specifies a regex that will redact sensitive source values in vulnerability reports.
|
|
463
|
+
*/
|
|
464
|
+
redactionValuePattern?: string
|
|
457
465
|
}
|
|
458
466
|
};
|
|
459
467
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.18.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@datadog/native-appsec": "^4.0.0",
|
|
72
|
-
"@datadog/native-iast-rewriter": "2.1
|
|
73
|
-
"@datadog/native-iast-taint-tracking": "1.6.
|
|
72
|
+
"@datadog/native-iast-rewriter": "2.2.1",
|
|
73
|
+
"@datadog/native-iast-taint-tracking": "1.6.3",
|
|
74
74
|
"@datadog/native-metrics": "^2.0.0",
|
|
75
|
-
"@datadog/pprof": "4.0.
|
|
75
|
+
"@datadog/pprof": "4.0.1",
|
|
76
76
|
"@datadog/sketches-js": "^2.1.0",
|
|
77
77
|
"@opentelemetry/api": "^1.0.0",
|
|
78
78
|
"@opentelemetry/core": "^1.14.0",
|
|
@@ -296,6 +296,11 @@ addHook({
|
|
|
296
296
|
const filteredPickles = getFilteredPickles(this, skippableSuites)
|
|
297
297
|
const { picklesToRun } = filteredPickles
|
|
298
298
|
isSuitesSkipped = picklesToRun.length !== this.pickleIds.length
|
|
299
|
+
|
|
300
|
+
log.debug(
|
|
301
|
+
() => `${picklesToRun.length} out of ${this.pickleIds.length} suites are going to run.`
|
|
302
|
+
)
|
|
303
|
+
|
|
299
304
|
this.pickleIds = picklesToRun
|
|
300
305
|
|
|
301
306
|
skippedSuites = Array.from(filteredPickles.skippedSuites)
|
|
@@ -208,6 +208,11 @@ addHook({
|
|
|
208
208
|
const rootDir = test && test.context && test.context.config && test.context.config.rootDir
|
|
209
209
|
|
|
210
210
|
const jestSuitesToRun = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
|
|
211
|
+
|
|
212
|
+
log.debug(
|
|
213
|
+
() => `${jestSuitesToRun.suitesToRun.length} out of ${shardedTests.length} suites are going to run.`
|
|
214
|
+
)
|
|
215
|
+
|
|
211
216
|
hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
|
|
212
217
|
hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
|
|
213
218
|
|
|
@@ -272,7 +277,16 @@ function cliWrapper (cli, jestVersion) {
|
|
|
272
277
|
|
|
273
278
|
const result = await runCLI.apply(this, arguments)
|
|
274
279
|
|
|
275
|
-
const {
|
|
280
|
+
const {
|
|
281
|
+
results: {
|
|
282
|
+
success,
|
|
283
|
+
coverageMap,
|
|
284
|
+
numFailedTestSuites,
|
|
285
|
+
numFailedTests,
|
|
286
|
+
numTotalTests,
|
|
287
|
+
numTotalTestSuites
|
|
288
|
+
}
|
|
289
|
+
} = result
|
|
276
290
|
|
|
277
291
|
let testCodeCoverageLinesTotal
|
|
278
292
|
try {
|
|
@@ -281,17 +295,30 @@ function cliWrapper (cli, jestVersion) {
|
|
|
281
295
|
} catch (e) {
|
|
282
296
|
// ignore errors
|
|
283
297
|
}
|
|
298
|
+
let status, error
|
|
299
|
+
|
|
300
|
+
if (success) {
|
|
301
|
+
if (numTotalTests === 0 && numTotalTestSuites === 0) {
|
|
302
|
+
status = 'skip'
|
|
303
|
+
} else {
|
|
304
|
+
status = 'pass'
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
status = 'fail'
|
|
308
|
+
error = new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
|
|
309
|
+
}
|
|
284
310
|
|
|
285
311
|
sessionAsyncResource.runInAsyncScope(() => {
|
|
286
312
|
testSessionFinishCh.publish({
|
|
287
|
-
status
|
|
313
|
+
status,
|
|
288
314
|
isSuitesSkipped,
|
|
289
315
|
isSuitesSkippingEnabled,
|
|
290
316
|
isCodeCoverageEnabled,
|
|
291
317
|
testCodeCoverageLinesTotal,
|
|
292
318
|
numSkippedSuites,
|
|
293
319
|
hasUnskippableSuites,
|
|
294
|
-
hasForcedToRunSuites
|
|
320
|
+
hasForcedToRunSuites,
|
|
321
|
+
error
|
|
295
322
|
})
|
|
296
323
|
})
|
|
297
324
|
|
|
@@ -363,23 +390,23 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
|
|
|
363
390
|
status = 'fail'
|
|
364
391
|
}
|
|
365
392
|
|
|
366
|
-
const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__)
|
|
367
|
-
.map(filename => getTestSuitePath(filename, environment.rootDir))
|
|
368
|
-
|
|
369
393
|
/**
|
|
370
394
|
* Child processes do not each request ITR configuration, so the jest's parent process
|
|
371
395
|
* needs to pass them the configuration. This is done via _ddTestCodeCoverageEnabled, which
|
|
372
396
|
* controls whether coverage is reported.
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
environment.
|
|
376
|
-
|
|
397
|
+
*/
|
|
398
|
+
if (environment.testEnvironmentOptions?._ddTestCodeCoverageEnabled) {
|
|
399
|
+
const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__)
|
|
400
|
+
.map(filename => getTestSuitePath(filename, environment.rootDir))
|
|
377
401
|
asyncResource.runInAsyncScope(() => {
|
|
378
402
|
testSuiteCodeCoverageCh.publish([...coverageFiles, environment.testSuite])
|
|
379
403
|
})
|
|
380
404
|
}
|
|
381
405
|
testSuiteFinishCh.publish({ status, errorMessage })
|
|
382
406
|
return suiteResults
|
|
407
|
+
}).catch(error => {
|
|
408
|
+
testSuiteFinishCh.publish({ status: 'fail', error })
|
|
409
|
+
throw error
|
|
383
410
|
})
|
|
384
411
|
})
|
|
385
412
|
})
|
|
@@ -508,6 +535,8 @@ addHook({
|
|
|
508
535
|
|
|
509
536
|
const jestSuitesToRun = getJestSuitesToRun(skippableSuites, tests, rootDir)
|
|
510
537
|
|
|
538
|
+
log.debug(() => `${jestSuitesToRun.suitesToRun.length} out of ${tests.length} suites are going to run.`)
|
|
539
|
+
|
|
511
540
|
hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
|
|
512
541
|
hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
|
|
513
542
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { addHook, channel } = require('./helpers/instrument')
|
|
3
|
+
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
4
4
|
const { wrapThen } = require('./helpers/promise')
|
|
5
5
|
const shimmer = require('../../datadog-shimmer')
|
|
6
6
|
|
|
@@ -39,34 +39,41 @@ addHook({
|
|
|
39
39
|
return raw.apply(this, arguments)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
43
|
+
|
|
42
44
|
function finish () {
|
|
43
45
|
finishRawQueryCh.publish()
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
return asyncResource.runInAsyncScope(() => {
|
|
49
|
+
startRawQueryCh.publish({ sql, dialect: this.dialect })
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
const rawResult = raw.apply(this, arguments)
|
|
52
|
+
shimmer.wrap(rawResult, 'then', originalThen => function () {
|
|
53
|
+
return asyncResource.runInAsyncScope(() => {
|
|
54
|
+
arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
|
|
55
|
+
if (arguments[1]) arguments[1] = wrapCallbackWithFinish(arguments[1], finish)
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
|
|
52
|
-
arguments[1] = wrapCallbackWithFinish(arguments[1], finish)
|
|
57
|
+
const originalThenResult = originalThen.apply(this, arguments)
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
shimmer.wrap(originalThenResult, 'catch', originalCatch => function () {
|
|
60
|
+
arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
|
|
61
|
+
return originalCatch.apply(this, arguments)
|
|
62
|
+
})
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return originalCatch.apply(this, arguments)
|
|
64
|
+
return originalThenResult
|
|
65
|
+
})
|
|
59
66
|
})
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
shimmer.wrap(rawResult, 'asCallback', originalAsCallback => function () {
|
|
69
|
+
return asyncResource.runInAsyncScope(() => {
|
|
70
|
+
arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
|
|
71
|
+
return originalAsCallback.apply(this, arguments)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
63
74
|
|
|
64
|
-
|
|
65
|
-
arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
|
|
66
|
-
return originalAsCallback.apply(this, arguments)
|
|
75
|
+
return rawResult
|
|
67
76
|
})
|
|
68
|
-
|
|
69
|
-
return rawResult
|
|
70
77
|
})
|
|
71
78
|
return Knex
|
|
72
79
|
})
|
|
@@ -133,11 +133,20 @@ function mochaHook (Runner) {
|
|
|
133
133
|
|
|
134
134
|
this.once('end', testRunAsyncResource.bind(function () {
|
|
135
135
|
let status = 'pass'
|
|
136
|
+
let error
|
|
136
137
|
if (this.stats) {
|
|
137
138
|
status = this.stats.failures === 0 ? 'pass' : 'fail'
|
|
139
|
+
if (this.stats.tests === 0) {
|
|
140
|
+
status = 'skip'
|
|
141
|
+
}
|
|
138
142
|
} else if (this.failures !== 0) {
|
|
139
143
|
status = 'fail'
|
|
140
144
|
}
|
|
145
|
+
|
|
146
|
+
if (status === 'fail') {
|
|
147
|
+
error = new Error(`Failed tests: ${this.failures}.`)
|
|
148
|
+
}
|
|
149
|
+
|
|
141
150
|
testFileToSuiteAr.clear()
|
|
142
151
|
|
|
143
152
|
let testCodeCoverageLinesTotal
|
|
@@ -157,7 +166,8 @@ function mochaHook (Runner) {
|
|
|
157
166
|
testCodeCoverageLinesTotal,
|
|
158
167
|
numSkippedSuites: skippedSuites.length,
|
|
159
168
|
hasForcedToRunSuites: isForcedToRun,
|
|
160
|
-
hasUnskippableSuites: !!unskippableSuites.length
|
|
169
|
+
hasUnskippableSuites: !!unskippableSuites.length,
|
|
170
|
+
error
|
|
161
171
|
})
|
|
162
172
|
}))
|
|
163
173
|
|
|
@@ -396,6 +406,11 @@ addHook({
|
|
|
396
406
|
const { suitesToRun } = filteredSuites
|
|
397
407
|
|
|
398
408
|
isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
|
|
409
|
+
|
|
410
|
+
log.debug(
|
|
411
|
+
() => `${suitesToRun.length} out of ${runner.suite.suites.length} suites are going to run.`
|
|
412
|
+
)
|
|
413
|
+
|
|
399
414
|
runner.suite.suites = suitesToRun
|
|
400
415
|
|
|
401
416
|
skippedSuites = Array.from(filteredSuites.skippedSuites)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
// TODO: either instrument all or none of the render functions
|
|
4
|
-
|
|
5
3
|
const { channel, addHook } = require('./helpers/instrument')
|
|
6
4
|
const shimmer = require('../../datadog-shimmer')
|
|
7
5
|
const { DD_MAJOR } = require('../../../version')
|
|
@@ -14,6 +12,9 @@ const bodyParsedChannel = channel('apm:next:body-parsed')
|
|
|
14
12
|
const queryParsedChannel = channel('apm:next:query-parsed')
|
|
15
13
|
|
|
16
14
|
const requests = new WeakSet()
|
|
15
|
+
const nodeNextRequestsToNextRequests = new WeakMap()
|
|
16
|
+
|
|
17
|
+
const MIDDLEWARE_HEADER = 'x-middleware-invoke'
|
|
17
18
|
|
|
18
19
|
function wrapHandleRequest (handleRequest) {
|
|
19
20
|
return function (req, res, pathname, query) {
|
|
@@ -56,18 +57,6 @@ function wrapHandleApiRequestWithMatch (handleApiRequest) {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function wrapRenderToResponse (renderToResponse) {
|
|
60
|
-
return function (ctx) {
|
|
61
|
-
return instrument(ctx.req, ctx.res, () => renderToResponse.apply(this, arguments))
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function wrapRenderErrorToResponse (renderErrorToResponse) {
|
|
66
|
-
return function (ctx) {
|
|
67
|
-
return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
60
|
function wrapRenderToHTML (renderToHTML) {
|
|
72
61
|
return function (req, res, pathname, query, parsedUrl) {
|
|
73
62
|
return instrument(req, res, () => renderToHTML.apply(this, arguments))
|
|
@@ -80,6 +69,18 @@ function wrapRenderErrorToHTML (renderErrorToHTML) {
|
|
|
80
69
|
}
|
|
81
70
|
}
|
|
82
71
|
|
|
72
|
+
function wrapRenderToResponse (renderToResponse) {
|
|
73
|
+
return function (ctx) {
|
|
74
|
+
return instrument(ctx.req, ctx.res, () => renderToResponse.apply(this, arguments))
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function wrapRenderErrorToResponse (renderErrorToResponse) {
|
|
79
|
+
return function (ctx) {
|
|
80
|
+
return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
83
84
|
function wrapFindPageComponents (findPageComponents) {
|
|
84
85
|
return function (pathname, query) {
|
|
85
86
|
const result = findPageComponents.apply(this, arguments)
|
|
@@ -114,7 +115,9 @@ function instrument (req, res, handler) {
|
|
|
114
115
|
req = req.originalRequest || req
|
|
115
116
|
res = res.originalResponse || res
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
// TODO support middleware properly in the future?
|
|
119
|
+
const isMiddleware = req.headers[MIDDLEWARE_HEADER]
|
|
120
|
+
if (isMiddleware || requests.has(req)) return handler()
|
|
118
121
|
|
|
119
122
|
requests.add(req)
|
|
120
123
|
|
|
@@ -154,6 +157,11 @@ function finish (ctx, result, err) {
|
|
|
154
157
|
errorChannel.publish(ctx)
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
const maybeNextRequest = nodeNextRequestsToNextRequests.get(ctx.req)
|
|
161
|
+
if (maybeNextRequest) {
|
|
162
|
+
ctx.nextRequest = maybeNextRequest
|
|
163
|
+
}
|
|
164
|
+
|
|
157
165
|
finishChannel.publish(ctx)
|
|
158
166
|
|
|
159
167
|
if (err) {
|
|
@@ -163,6 +171,24 @@ function finish (ctx, result, err) {
|
|
|
163
171
|
return result
|
|
164
172
|
}
|
|
165
173
|
|
|
174
|
+
// also wrapped in dist/server/future/route-handlers/app-route-route-handler.js
|
|
175
|
+
// in versions below 13.3.0 that support middleware,
|
|
176
|
+
// however, it is not provided as a class function or exported property
|
|
177
|
+
addHook({
|
|
178
|
+
name: 'next',
|
|
179
|
+
versions: ['>=13.3.0'],
|
|
180
|
+
file: 'dist/server/web/spec-extension/adapters/next-request.js'
|
|
181
|
+
}, NextRequestAdapter => {
|
|
182
|
+
shimmer.wrap(NextRequestAdapter.NextRequestAdapter, 'fromNodeNextRequest', fromNodeNextRequest => {
|
|
183
|
+
return function (nodeNextRequest) {
|
|
184
|
+
const nextRequest = fromNodeNextRequest.apply(this, arguments)
|
|
185
|
+
nodeNextRequestsToNextRequests.set(nodeNextRequest.originalRequest, nextRequest)
|
|
186
|
+
return nextRequest
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
return NextRequestAdapter
|
|
190
|
+
})
|
|
191
|
+
|
|
166
192
|
addHook({
|
|
167
193
|
name: 'next',
|
|
168
194
|
versions: ['>=11.1'],
|
|
@@ -175,27 +201,32 @@ addHook({
|
|
|
175
201
|
file: 'dist/next-server/server/serve-static.js'
|
|
176
202
|
}, serveStatic => shimmer.wrap(serveStatic, 'serveStatic', wrapServeStatic))
|
|
177
203
|
|
|
178
|
-
addHook({ name: 'next', versions: ['>=
|
|
204
|
+
addHook({ name: 'next', versions: ['>=11.1'], file: 'dist/server/next-server.js' }, nextServer => {
|
|
179
205
|
const Server = nextServer.default
|
|
180
206
|
|
|
181
207
|
shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
|
|
182
|
-
|
|
208
|
+
|
|
209
|
+
// Wrapping these makes sure any public API render methods called in a custom server
|
|
210
|
+
// are traced properly
|
|
211
|
+
// (instead of wrapping the top-level API methods, just wrapping these covers them all)
|
|
183
212
|
shimmer.wrap(Server.prototype, 'renderToResponse', wrapRenderToResponse)
|
|
184
213
|
shimmer.wrap(Server.prototype, 'renderErrorToResponse', wrapRenderErrorToResponse)
|
|
214
|
+
|
|
185
215
|
shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
|
|
186
216
|
|
|
187
217
|
return nextServer
|
|
188
218
|
})
|
|
189
219
|
|
|
190
|
-
|
|
220
|
+
// `handleApiRequest` changes parameters/implementation at 13.2.0
|
|
221
|
+
addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
|
|
191
222
|
const Server = nextServer.default
|
|
223
|
+
shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
|
|
224
|
+
return nextServer
|
|
225
|
+
})
|
|
192
226
|
|
|
193
|
-
|
|
227
|
+
addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-server.js' }, nextServer => {
|
|
228
|
+
const Server = nextServer.default
|
|
194
229
|
shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequest)
|
|
195
|
-
shimmer.wrap(Server.prototype, 'renderToResponse', wrapRenderToResponse)
|
|
196
|
-
shimmer.wrap(Server.prototype, 'renderErrorToResponse', wrapRenderErrorToResponse)
|
|
197
|
-
shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
|
|
198
|
-
|
|
199
230
|
return nextServer
|
|
200
231
|
})
|
|
201
232
|
|
|
@@ -208,8 +239,12 @@ addHook({
|
|
|
208
239
|
|
|
209
240
|
shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
|
|
210
241
|
shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequest)
|
|
242
|
+
|
|
243
|
+
// Likewise with newer versions, these correlate to public API render methods for custom servers
|
|
244
|
+
// all public ones use these methods somewhere in their code path
|
|
211
245
|
shimmer.wrap(Server.prototype, 'renderToHTML', wrapRenderToHTML)
|
|
212
246
|
shimmer.wrap(Server.prototype, 'renderErrorToHTML', wrapRenderErrorToHTML)
|
|
247
|
+
|
|
213
248
|
shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
|
|
214
249
|
|
|
215
250
|
return nextServer
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
2
2
|
const shimmer = require('../../datadog-shimmer')
|
|
3
|
+
const { parseAnnotations } = require('../../dd-trace/src/plugins/util/test')
|
|
3
4
|
|
|
4
5
|
const testStartCh = channel('ci:playwright:test:start')
|
|
5
6
|
const testFinishCh = channel('ci:playwright:test:finish')
|
|
@@ -103,7 +104,11 @@ function testBeginHandler (test) {
|
|
|
103
104
|
})
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
function testEndHandler (test, testStatus, error) {
|
|
107
|
+
function testEndHandler (test, annotations, testStatus, error) {
|
|
108
|
+
let annotationTags
|
|
109
|
+
if (annotations.length) {
|
|
110
|
+
annotationTags = parseAnnotations(annotations)
|
|
111
|
+
}
|
|
107
112
|
const { _requireFile: testSuiteAbsolutePath, results, _type } = test
|
|
108
113
|
|
|
109
114
|
if (_type === 'beforeAll' || _type === 'afterAll') {
|
|
@@ -113,7 +118,7 @@ function testEndHandler (test, testStatus, error) {
|
|
|
113
118
|
const testResult = results[results.length - 1]
|
|
114
119
|
const testAsyncResource = testToAr.get(test)
|
|
115
120
|
testAsyncResource.runInAsyncScope(() => {
|
|
116
|
-
testFinishCh.publish({ testStatus, steps: testResult.steps, error })
|
|
121
|
+
testFinishCh.publish({ testStatus, steps: testResult.steps, error, extraTags: annotationTags })
|
|
117
122
|
})
|
|
118
123
|
|
|
119
124
|
if (!testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
|
|
@@ -172,7 +177,7 @@ function dispatcherHook (dispatcherExport) {
|
|
|
172
177
|
const { results } = test
|
|
173
178
|
const testResult = results[results.length - 1]
|
|
174
179
|
|
|
175
|
-
testEndHandler(test, STATUS_TO_TEST_STATUS[testResult.status], testResult.error)
|
|
180
|
+
testEndHandler(test, params.annotations, STATUS_TO_TEST_STATUS[testResult.status], testResult.error)
|
|
176
181
|
}
|
|
177
182
|
})
|
|
178
183
|
|
|
@@ -200,10 +205,10 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
200
205
|
const test = getTestByTestId(dispatcher, testId)
|
|
201
206
|
testBeginHandler(test)
|
|
202
207
|
})
|
|
203
|
-
worker.on('testEnd', ({ testId, status, errors }) => {
|
|
208
|
+
worker.on('testEnd', ({ testId, status, errors, annotations }) => {
|
|
204
209
|
const test = getTestByTestId(dispatcher, testId)
|
|
205
210
|
|
|
206
|
-
testEndHandler(test, STATUS_TO_TEST_STATUS[status], errors && errors[0])
|
|
211
|
+
testEndHandler(test, annotations, STATUS_TO_TEST_STATUS[status], errors && errors[0])
|
|
207
212
|
})
|
|
208
213
|
|
|
209
214
|
return worker
|
|
@@ -230,7 +235,7 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
230
235
|
// because they were skipped
|
|
231
236
|
tests.forEach(test => {
|
|
232
237
|
testBeginHandler(test)
|
|
233
|
-
testEndHandler(test, 'skip')
|
|
238
|
+
testEndHandler(test, [], 'skip')
|
|
234
239
|
})
|
|
235
240
|
})
|
|
236
241
|
|
|
@@ -76,6 +76,7 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
finish ({ req, res, span }) {
|
|
79
|
+
if (!span) return
|
|
79
80
|
if (res) {
|
|
80
81
|
const status = res.status || res.statusCode
|
|
81
82
|
|
|
@@ -98,6 +99,7 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
error ({ span, error }) {
|
|
102
|
+
if (!span) return
|
|
101
103
|
if (error) {
|
|
102
104
|
span.addTags({
|
|
103
105
|
[ERROR_TYPE]: error.name,
|
|
@@ -54,11 +54,17 @@ class JestPlugin extends CiPlugin {
|
|
|
54
54
|
testCodeCoverageLinesTotal,
|
|
55
55
|
numSkippedSuites,
|
|
56
56
|
hasUnskippableSuites,
|
|
57
|
-
hasForcedToRunSuites
|
|
57
|
+
hasForcedToRunSuites,
|
|
58
|
+
error
|
|
58
59
|
}) => {
|
|
59
60
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
60
61
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
61
62
|
|
|
63
|
+
if (error) {
|
|
64
|
+
this.testSessionSpan.setTag('error', error)
|
|
65
|
+
this.testModuleSpan.setTag('error', error)
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
addIntelligentTestRunnerSpanTags(
|
|
63
69
|
this.testSessionSpan,
|
|
64
70
|
this.testModuleSpan,
|
|
@@ -150,9 +156,11 @@ class JestPlugin extends CiPlugin {
|
|
|
150
156
|
})
|
|
151
157
|
})
|
|
152
158
|
|
|
153
|
-
this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage }) => {
|
|
159
|
+
this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
|
|
154
160
|
this.testSuiteSpan.setTag(TEST_STATUS, status)
|
|
155
|
-
if (
|
|
161
|
+
if (error) {
|
|
162
|
+
this.testSuiteSpan.setTag('error', error)
|
|
163
|
+
} else if (errorMessage) {
|
|
156
164
|
this.testSuiteSpan.setTag('error', new Error(errorMessage))
|
|
157
165
|
}
|
|
158
166
|
this.testSuiteSpan.finish()
|
|
@@ -150,13 +150,19 @@ class MochaPlugin extends CiPlugin {
|
|
|
150
150
|
testCodeCoverageLinesTotal,
|
|
151
151
|
numSkippedSuites,
|
|
152
152
|
hasForcedToRunSuites,
|
|
153
|
-
hasUnskippableSuites
|
|
153
|
+
hasUnskippableSuites,
|
|
154
|
+
error
|
|
154
155
|
}) => {
|
|
155
156
|
if (this.testSessionSpan) {
|
|
156
157
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
|
|
157
158
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
158
159
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
159
160
|
|
|
161
|
+
if (error) {
|
|
162
|
+
this.testSessionSpan.setTag('error', error)
|
|
163
|
+
this.testModuleSpan.setTag('error', error)
|
|
164
|
+
}
|
|
165
|
+
|
|
160
166
|
addIntelligentTestRunnerSpanTags(
|
|
161
167
|
this.testSessionSpan,
|
|
162
168
|
this.testModuleSpan,
|
|
@@ -43,7 +43,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
43
43
|
this.addError(error, span)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
finish ({ req, res }) {
|
|
46
|
+
finish ({ req, res, nextRequest = {} }) {
|
|
47
47
|
const store = storage.getStore()
|
|
48
48
|
|
|
49
49
|
if (!store) return
|
|
@@ -52,7 +52,8 @@ class NextPlugin extends ServerPlugin {
|
|
|
52
52
|
const error = span.context()._tags['error']
|
|
53
53
|
|
|
54
54
|
if (!this.config.validateStatus(res.statusCode) && !error) {
|
|
55
|
-
span.setTag('error', true)
|
|
55
|
+
span.setTag('error', req.error || nextRequest.error || true)
|
|
56
|
+
web.addError(req, req.error || nextRequest.error || true)
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
span.addTags({
|
|
@@ -64,7 +65,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
64
65
|
span.finish()
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
pageLoad ({ page, isAppPath }) {
|
|
68
|
+
pageLoad ({ page, isAppPath = false }) {
|
|
68
69
|
const store = storage.getStore()
|
|
69
70
|
|
|
70
71
|
if (!store) return
|
|
@@ -72,7 +72,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
72
72
|
|
|
73
73
|
this.enter(span, store)
|
|
74
74
|
})
|
|
75
|
-
this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error }) => {
|
|
75
|
+
this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags }) => {
|
|
76
76
|
const store = storage.getStore()
|
|
77
77
|
const span = store && store.span
|
|
78
78
|
if (!span) return
|
|
@@ -82,6 +82,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
82
82
|
if (error) {
|
|
83
83
|
span.setTag('error', error)
|
|
84
84
|
}
|
|
85
|
+
if (extraTags) {
|
|
86
|
+
span.addTags(extraTags)
|
|
87
|
+
}
|
|
85
88
|
|
|
86
89
|
steps.forEach(step => {
|
|
87
90
|
const stepStartTime = step.startTime.getTime()
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
|
|
5
|
+
'HARCODED_SECRET_ANALYZER': require('./hardcoded-secret-analyzer'),
|
|
5
6
|
'HSTS_HEADER_MISSING_ANALYZER': require('./hsts-header-missing-analyzer'),
|
|
6
7
|
'INSECURE_COOKIE_ANALYZER': require('./insecure-cookie-analyzer'),
|
|
7
8
|
'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Analyzer = require('./vulnerability-analyzer')
|
|
4
|
+
const { HARDCODED_SECRET } = require('../vulnerabilities')
|
|
5
|
+
const { getRelativePath } = require('../path-line')
|
|
6
|
+
|
|
7
|
+
const secretRules = require('./hardcoded-secrets-rules')
|
|
8
|
+
|
|
9
|
+
class HardcodedSecretAnalyzer extends Analyzer {
|
|
10
|
+
constructor () {
|
|
11
|
+
super(HARDCODED_SECRET)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
onConfigure () {
|
|
15
|
+
this.addSub('datadog:secrets:result', (secrets) => { this.analyze(secrets) })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
analyze (secrets) {
|
|
19
|
+
if (!secrets?.file || !secrets.literals) return
|
|
20
|
+
|
|
21
|
+
const matches = secrets.literals
|
|
22
|
+
.filter(literal => literal.value && literal.locations?.length)
|
|
23
|
+
.map(literal => {
|
|
24
|
+
const match = secretRules.find(rule => literal.value.match(rule.regex))
|
|
25
|
+
|
|
26
|
+
return match ? { locations: literal.locations, ruleId: match.id } : undefined
|
|
27
|
+
})
|
|
28
|
+
.filter(match => !!match)
|
|
29
|
+
|
|
30
|
+
if (matches.length) {
|
|
31
|
+
const file = getRelativePath(secrets.file)
|
|
32
|
+
|
|
33
|
+
matches.forEach(match => {
|
|
34
|
+
match.locations
|
|
35
|
+
.filter(location => location.line)
|
|
36
|
+
.forEach(location => this._report({
|
|
37
|
+
file,
|
|
38
|
+
line: location.line,
|
|
39
|
+
column: location.column,
|
|
40
|
+
data: match.ruleId
|
|
41
|
+
}))
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_getEvidence (value) {
|
|
47
|
+
return { value: `${value.data}` }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_getLocation (value) {
|
|
51
|
+
return {
|
|
52
|
+
path: value.file,
|
|
53
|
+
line: value.line,
|
|
54
|
+
column: value.column,
|
|
55
|
+
isInternal: false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = new HardcodedSecretAnalyzer()
|