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.
Files changed (39) hide show
  1. package/index.d.ts +9 -1
  2. package/package.json +4 -4
  3. package/packages/datadog-instrumentations/src/cucumber.js +5 -0
  4. package/packages/datadog-instrumentations/src/jest.js +39 -10
  5. package/packages/datadog-instrumentations/src/knex.js +24 -17
  6. package/packages/datadog-instrumentations/src/mocha.js +16 -1
  7. package/packages/datadog-instrumentations/src/next.js +58 -23
  8. package/packages/datadog-instrumentations/src/playwright.js +11 -6
  9. package/packages/datadog-plugin-http/src/client.js +2 -0
  10. package/packages/datadog-plugin-jest/src/index.js +11 -3
  11. package/packages/datadog-plugin-mocha/src/index.js +7 -1
  12. package/packages/datadog-plugin-next/src/index.js +4 -3
  13. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  14. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  15. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
  16. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
  17. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
  18. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
  19. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +9 -2
  20. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
  21. package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
  22. package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
  24. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
  25. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +19 -0
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +2 -1
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  28. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
  29. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
  30. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
  31. package/packages/dd-trace/src/config.js +29 -13
  32. package/packages/dd-trace/src/git_properties.js +16 -15
  33. package/packages/dd-trace/src/plugins/util/test.js +29 -1
  34. package/packages/dd-trace/src/profiling/config.js +3 -17
  35. package/packages/dd-trace/src/profiling/profilers/wall.js +44 -39
  36. package/packages/dd-trace/src/telemetry/index.js +4 -0
  37. package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
  38. package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
  39. 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.17.0",
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.3",
73
- "@datadog/native-iast-taint-tracking": "1.6.1",
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.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 { results: { success, coverageMap } } = result
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: success ? 'pass' : 'fail',
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 (coverageFiles &&
375
- environment.testEnvironmentOptions &&
376
- environment.testEnvironmentOptions._ddTestCodeCoverageEnabled) {
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
- startRawQueryCh.publish({ sql, dialect: this.dialect })
48
+ return asyncResource.runInAsyncScope(() => {
49
+ startRawQueryCh.publish({ sql, dialect: this.dialect })
47
50
 
48
- const rawResult = raw.apply(this, arguments)
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
- shimmer.wrap(rawResult, 'then', originalThen => function () {
51
- arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
52
- arguments[1] = wrapCallbackWithFinish(arguments[1], finish)
57
+ const originalThenResult = originalThen.apply(this, arguments)
53
58
 
54
- const originalThenResult = originalThen.apply(this, arguments)
59
+ shimmer.wrap(originalThenResult, 'catch', originalCatch => function () {
60
+ arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
61
+ return originalCatch.apply(this, arguments)
62
+ })
55
63
 
56
- shimmer.wrap(originalThenResult, 'catch', originalCatch => function () {
57
- arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
58
- return originalCatch.apply(this, arguments)
64
+ return originalThenResult
65
+ })
59
66
  })
60
67
 
61
- return originalThenResult
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
- shimmer.wrap(rawResult, 'asCallback', originalAsCallback => function () {
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
- if (requests.has(req)) return handler()
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: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
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
- shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
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
- addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-server.js' }, nextServer => {
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
- shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
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 (errorMessage) {
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()