dd-trace 4.42.0 → 4.43.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 (43) hide show
  1. package/LICENSE-3rdparty.csv +0 -2
  2. package/index.d.ts +61 -39
  3. package/package.json +7 -11
  4. package/packages/datadog-instrumentations/src/child_process.js +2 -2
  5. package/packages/datadog-instrumentations/src/fs.js +1 -1
  6. package/packages/datadog-instrumentations/src/hapi.js +1 -1
  7. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  8. package/packages/datadog-instrumentations/src/jest.js +17 -2
  9. package/packages/datadog-instrumentations/src/kafkajs.js +1 -1
  10. package/packages/datadog-instrumentations/src/ldapjs.js +2 -2
  11. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  12. package/packages/datadog-instrumentations/src/next.js +1 -1
  13. package/packages/datadog-instrumentations/src/pg.js +2 -2
  14. package/packages/datadog-instrumentations/src/playwright.js +46 -32
  15. package/packages/datadog-instrumentations/src/restify.js +1 -1
  16. package/packages/datadog-instrumentations/src/vitest.js +51 -5
  17. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  18. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
  20. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -4
  21. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +79 -42
  22. package/packages/datadog-plugin-cypress/src/plugin.js +4 -3
  23. package/packages/datadog-plugin-fs/src/index.js +1 -1
  24. package/packages/datadog-plugin-jest/src/index.js +7 -1
  25. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -1
  26. package/packages/datadog-plugin-mongodb-core/src/index.js +1 -1
  27. package/packages/datadog-plugin-openai/src/index.js +5 -5
  28. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  29. package/packages/datadog-plugin-sharedb/src/index.js +1 -1
  30. package/packages/datadog-plugin-vitest/src/index.js +17 -6
  31. package/packages/dd-trace/src/analytics_sampler.js +1 -1
  32. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +1 -1
  33. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  34. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +3 -1
  36. package/packages/dd-trace/src/appsec/index.js +3 -3
  37. package/packages/dd-trace/src/appsec/passport.js +1 -1
  38. package/packages/dd-trace/src/appsec/reporter.js +0 -4
  39. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
  40. package/packages/dd-trace/src/config.js +27 -24
  41. package/packages/dd-trace/src/datastreams/processor.js +1 -1
  42. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  43. package/packages/dd-trace/src/opentelemetry/tracer.js +6 -0
@@ -21,7 +21,7 @@ class DynamoDb extends BaseAwsSdkPlugin {
21
21
  // batch operations have different format, collect table name for batch
22
22
  // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#batchGetItem-property`
23
23
  // dynamoDB batch TableName
24
- if (params.RequestItems) {
24
+ if (params.RequestItems !== null) {
25
25
  if (typeof params.RequestItems === 'object') {
26
26
  if (Object.keys(params.RequestItems).length === 1) {
27
27
  const tableName = Object.keys(params.RequestItems)[0]
@@ -47,7 +47,7 @@ class Stepfunctions extends BaseAwsSdkPlugin {
47
47
 
48
48
  try {
49
49
  const inputObj = JSON.parse(input)
50
- if (inputObj && typeof inputObj === 'object') {
50
+ if (inputObj !== null && typeof inputObj === 'object') {
51
51
  // We've parsed the input JSON string
52
52
  inputObj._datadog = {}
53
53
  this.tracer.inject(span, 'text_map', inputObj._datadog)
@@ -7,10 +7,10 @@ const PROCESS_DENYLIST = ['md5']
7
7
 
8
8
  const VARNAMES_REGEX = /\$([\w\d_]*)(?:[^\w\d_]|$)/gmi
9
9
  // eslint-disable-next-line max-len
10
- const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|api_?key|secret|a(?:ccess|uth)_token|mysql_pwd|credentials|(?:stripe)?token)$'
10
+ const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|address|api[-_]?key|e?mail|secret(?:[-_]?key)?|a(?:ccess|uth)[-_]?token|mysql_pwd|credentials|(?:stripe)?token)$'
11
11
  const regexParam = new RegExp(PARAM_PATTERN, 'i')
12
12
  const ENV_PATTERN = '^(\\w+=\\w+;)*\\w+=\\w+;?$'
13
- const envvarRegex = new RegExp(ENV_PATTERN)
13
+ const envVarRegex = new RegExp(ENV_PATTERN)
14
14
  const REDACTED = '?'
15
15
 
16
16
  function extractVarNames (expression) {
@@ -61,7 +61,9 @@ function scrubChildProcessCmd (expression) {
61
61
  for (let index = 0; index < expressionTokens.length; index++) {
62
62
  const token = expressionTokens[index]
63
63
 
64
- if (typeof token === 'object') {
64
+ if (token === null) {
65
+ continue
66
+ } else if (typeof token === 'object') {
65
67
  if (token.pattern) {
66
68
  result.push(token.pattern)
67
69
  } else if (token.op) {
@@ -70,7 +72,7 @@ function scrubChildProcessCmd (expression) {
70
72
  result.push(`#${token.comment}`)
71
73
  }
72
74
  } else if (!foundBinary) {
73
- if (envvarRegex.test(token)) {
75
+ if (envVarRegex.test(token)) {
74
76
  const envSplit = token.split('=')
75
77
 
76
78
  if (!ALLOWED_ENV_VARIABLES.includes(envSplit[0])) {
@@ -28,7 +28,8 @@ const {
28
28
  TEST_SOURCE_FILE,
29
29
  TEST_IS_NEW,
30
30
  TEST_IS_RETRY,
31
- TEST_EARLY_FLAKE_ENABLED
31
+ TEST_EARLY_FLAKE_ENABLED,
32
+ NUM_FAILED_TEST_RETRIES
32
33
  } = require('../../dd-trace/src/plugins/util/test')
33
34
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
34
35
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -207,10 +208,40 @@ class CypressPlugin {
207
208
  this.knownTests = []
208
209
  }
209
210
 
211
+ // Init function returns a promise that resolves with the Cypress configuration
212
+ // Depending on the received configuration, the Cypress configuration can be modified:
213
+ // for example, to enable retries for failed tests.
210
214
  init (tracer, cypressConfig) {
211
215
  this._isInit = true
212
216
  this.tracer = tracer
213
217
  this.cypressConfig = cypressConfig
218
+
219
+ this.libraryConfigurationPromise = getLibraryConfiguration(this.tracer, this.testConfiguration)
220
+ .then((libraryConfigurationResponse) => {
221
+ if (libraryConfigurationResponse.err) {
222
+ log.error(libraryConfigurationResponse.err)
223
+ } else {
224
+ const {
225
+ libraryConfig: {
226
+ isSuitesSkippingEnabled,
227
+ isCodeCoverageEnabled,
228
+ isEarlyFlakeDetectionEnabled,
229
+ earlyFlakeDetectionNumRetries,
230
+ isFlakyTestRetriesEnabled
231
+ }
232
+ } = libraryConfigurationResponse
233
+ this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
234
+ this.isCodeCoverageEnabled = isCodeCoverageEnabled
235
+ this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
236
+ this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
237
+ this.isFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
238
+ if (this.isFlakyTestRetriesEnabled) {
239
+ this.cypressConfig.retries.runMode = NUM_FAILED_TEST_RETRIES
240
+ }
241
+ }
242
+ return this.cypressConfig
243
+ })
244
+ return this.libraryConfigurationPromise
214
245
  }
215
246
 
216
247
  getTestSuiteSpan (suite) {
@@ -297,29 +328,13 @@ class CypressPlugin {
297
328
  }
298
329
 
299
330
  async beforeRun (details) {
331
+ // We need to make sure that the plugin is initialized before running the tests
332
+ // This is for the case where the user has not returned the promise from the init function
333
+ await this.libraryConfigurationPromise
300
334
  this.command = getCypressCommand(details)
301
335
  this.frameworkVersion = getCypressVersion(details)
302
336
  this.rootDir = getRootDir(details)
303
337
 
304
- const libraryConfigurationResponse = await getLibraryConfiguration(this.tracer, this.testConfiguration)
305
-
306
- if (libraryConfigurationResponse.err) {
307
- log.error(libraryConfigurationResponse.err)
308
- } else {
309
- const {
310
- libraryConfig: {
311
- isSuitesSkippingEnabled,
312
- isCodeCoverageEnabled,
313
- isEarlyFlakeDetectionEnabled,
314
- earlyFlakeDetectionNumRetries
315
- }
316
- } = libraryConfigurationResponse
317
- this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
318
- this.isCodeCoverageEnabled = isCodeCoverageEnabled
319
- this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
320
- this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
321
- }
322
-
323
338
  if (this.isEarlyFlakeDetectionEnabled) {
324
339
  const knownTestsResponse = await getKnownTests(
325
340
  this.tracer,
@@ -485,29 +500,51 @@ class CypressPlugin {
485
500
  // This is not always the case, such as when an `after` hook fails:
486
501
  // Cypress will report the last run test as failed, but we don't know that yet at `dd:afterEach`
487
502
  let latestError
488
- finishedTests.forEach((finishedTest) => {
489
- const cypressTest = cypressTests.find(test => test.title.join(' ') === finishedTest.testName)
490
- if (!cypressTest) {
491
- return
492
- }
493
- if (cypressTest.displayError) {
494
- latestError = new Error(cypressTest.displayError)
495
- }
496
- const cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
497
- // update test status
498
- if (cypressTestStatus !== finishedTest.testStatus) {
499
- finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
500
- finishedTest.testSpan.setTag('error', latestError)
501
- }
502
- if (this.itrCorrelationId) {
503
- finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
504
- }
505
- if (spec.absolute && this.repositoryRoot) {
506
- finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, this.repositoryRoot))
507
- } else {
508
- finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative)
503
+
504
+ const finishedTestsByTestName = finishedTests.reduce((acc, finishedTest) => {
505
+ if (!acc[finishedTest.testName]) {
506
+ acc[finishedTest.testName] = []
509
507
  }
510
- finishedTest.testSpan.finish(finishedTest.finishTime)
508
+ acc[finishedTest.testName].push(finishedTest)
509
+ return acc
510
+ }, {})
511
+
512
+ Object.entries(finishedTestsByTestName).forEach(([testName, finishedTestAttempts]) => {
513
+ finishedTestAttempts.forEach((finishedTest, attemptIndex) => {
514
+ // TODO: there could be multiple if there have been retries!
515
+ // potentially we need to match the test status!
516
+ const cypressTest = cypressTests.find(test => test.title.join(' ') === testName)
517
+ if (!cypressTest) {
518
+ return
519
+ }
520
+ // finishedTests can include multiple tests with the same name if they have been retried
521
+ // by early flake detection. Cypress is unaware of this so .attempts does not necessarily have
522
+ // the same length as `finishedTestAttempts`
523
+ let cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
524
+ if (cypressTest.attempts && cypressTest.attempts[attemptIndex]) {
525
+ cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.attempts[attemptIndex].state]
526
+ if (attemptIndex > 0) {
527
+ finishedTest.testSpan.setTag(TEST_IS_RETRY, 'true')
528
+ }
529
+ }
530
+ if (cypressTest.displayError) {
531
+ latestError = new Error(cypressTest.displayError)
532
+ }
533
+ // Update test status
534
+ if (cypressTestStatus !== finishedTest.testStatus) {
535
+ finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
536
+ finishedTest.testSpan.setTag('error', latestError)
537
+ }
538
+ if (this.itrCorrelationId) {
539
+ finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
540
+ }
541
+ if (spec.absolute && this.repositoryRoot) {
542
+ finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, this.repositoryRoot))
543
+ } else {
544
+ finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative)
545
+ }
546
+ finishedTest.testSpan.finish(finishedTest.finishTime)
547
+ })
511
548
  })
512
549
 
513
550
  if (this.testSuiteSpan) {
@@ -22,13 +22,14 @@ module.exports = (on, config) => {
22
22
  // The tracer was not init correctly for whatever reason (such as invalid DD_SITE)
23
23
  if (tracer._tracer instanceof NoopTracer) {
24
24
  // We still need to register these tasks or the support file will fail
25
- return on('task', noopTask)
25
+ on('task', noopTask)
26
+ return config
26
27
  }
27
28
 
28
- cypressPlugin.init(tracer, config)
29
-
30
29
  on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
31
30
  on('after:spec', cypressPlugin.afterSpec.bind(cypressPlugin))
32
31
  on('after:run', cypressPlugin.afterRun.bind(cypressPlugin))
33
32
  on('task', cypressPlugin.getTasks())
33
+
34
+ return cypressPlugin.init(tracer, config)
34
35
  }
@@ -29,7 +29,7 @@ class FsPlugin extends TracingPlugin {
29
29
  resource: operation,
30
30
  kind: 'internal',
31
31
  meta: {
32
- 'file.descriptor': (typeof fd === 'object' || typeof fd === 'number') ? fd.toString() : '',
32
+ 'file.descriptor': ((fd !== null && typeof fd === 'object') || typeof fd === 'number') ? fd.toString() : '',
33
33
  'file.dest': params.dest || params.newPath || (params.target && params.path),
34
34
  'file.flag': String(flag || defaultFlag || ''),
35
35
  'file.gid': gid || '',
@@ -148,6 +148,7 @@ class JestPlugin extends CiPlugin {
148
148
  config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
149
149
  config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
150
150
  config._ddRepositoryRoot = this.repositoryRoot
151
+ config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
151
152
  })
152
153
  })
153
154
 
@@ -324,7 +325,8 @@ class JestPlugin extends CiPlugin {
324
325
  testStartLine,
325
326
  testSourceFile,
326
327
  isNew,
327
- isEfdRetry
328
+ isEfdRetry,
329
+ isJestRetry
328
330
  } = test
329
331
 
330
332
  const extraTags = {
@@ -349,6 +351,10 @@ class JestPlugin extends CiPlugin {
349
351
  }
350
352
  }
351
353
 
354
+ if (isJestRetry) {
355
+ extraTags[TEST_IS_RETRY] = 'true'
356
+ }
357
+
352
358
  return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
353
359
  }
354
360
  }
@@ -81,7 +81,7 @@ class KafkajsProducerPlugin extends ProducerPlugin {
81
81
  span.setTag(BOOTSTRAP_SERVERS_KEY, bootstrapServers)
82
82
  }
83
83
  for (const message of messages) {
84
- if (typeof message === 'object') {
84
+ if (message !== null && typeof message === 'object') {
85
85
  this.tracer.inject(span, 'text_map', message.headers)
86
86
  if (this.config.dsmEnabled) {
87
87
  const payloadSize = getMessageSize(message)
@@ -115,7 +115,7 @@ function limitDepth (input) {
115
115
  }
116
116
 
117
117
  function isObject (val) {
118
- return typeof val === 'object' && val !== null && !(val instanceof Array)
118
+ return val !== null && typeof val === 'object' && !Array.isArray(val)
119
119
  }
120
120
 
121
121
  function isBSON (val) {
@@ -118,7 +118,7 @@ class OpenApiPlugin extends TracingPlugin {
118
118
  }
119
119
 
120
120
  // createChatCompletion, createCompletion
121
- if (typeof payload.logit_bias === 'object' && payload.logit_bias) {
121
+ if (payload.logit_bias !== null && typeof payload.logit_bias === 'object') {
122
122
  for (const [tokenId, bias] of Object.entries(payload.logit_bias)) {
123
123
  tags[`openai.request.logit_bias.${tokenId}`] = bias
124
124
  }
@@ -427,14 +427,14 @@ function createChatCompletionRequestExtraction (tags, payload, store) {
427
427
  function commonCreateImageRequestExtraction (tags, payload, store) {
428
428
  // createImageEdit, createImageVariation
429
429
  const img = payload.file || payload.image
430
- if (img && typeof img === 'object' && img.path) {
430
+ if (img !== null && typeof img === 'object' && img.path) {
431
431
  const file = path.basename(img.path)
432
432
  tags['openai.request.image'] = file
433
433
  store.file = file
434
434
  }
435
435
 
436
436
  // createImageEdit
437
- if (payload.mask && typeof payload.mask === 'object' && payload.mask.path) {
437
+ if (payload.mask !== null && typeof payload.mask === 'object' && payload.mask.path) {
438
438
  const mask = path.basename(payload.mask.path)
439
439
  tags['openai.request.mask'] = mask
440
440
  store.mask = mask
@@ -633,7 +633,7 @@ function commonCreateAudioRequestExtraction (tags, body, store) {
633
633
  tags['openai.request.response_format'] = body.response_format
634
634
  tags['openai.request.language'] = body.language
635
635
 
636
- if (body.file && typeof body.file === 'object' && body.file.path) {
636
+ if (body.file !== null && typeof body.file === 'object' && body.file.path) {
637
637
  const filename = path.basename(body.file.path)
638
638
  tags['openai.request.filename'] = filename
639
639
  store.file = filename
@@ -646,7 +646,7 @@ function commonFileRequestExtraction (tags, body) {
646
646
  // User can provider either exact file contents or a file read stream
647
647
  // With the stream we extract the filepath
648
648
  // This is a best effort attempt to extract the filename during the request
649
- if (body.file && typeof body.file === 'object' && body.file.path) {
649
+ if (body.file !== null && typeof body.file === 'object' && body.file.path) {
650
650
  tags['openai.request.filename'] = path.basename(body.file.path)
651
651
  }
652
652
  }
@@ -116,7 +116,7 @@ class PlaywrightPlugin extends CiPlugin {
116
116
 
117
117
  this.enter(span, store)
118
118
  })
119
- this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry }) => {
119
+ this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry, isRetry }) => {
120
120
  const store = storage.getStore()
121
121
  const span = store && store.span
122
122
  if (!span) return
@@ -135,6 +135,9 @@ class PlaywrightPlugin extends CiPlugin {
135
135
  span.setTag(TEST_IS_RETRY, 'true')
136
136
  }
137
137
  }
138
+ if (isRetry) {
139
+ span.setTag(TEST_IS_RETRY, 'true')
140
+ }
138
141
 
139
142
  steps.forEach(step => {
140
143
  const stepStartTime = step.startTime.getTime()
@@ -54,7 +54,7 @@ function sanitize (input) {
54
54
  }
55
55
 
56
56
  function isObject (val) {
57
- return typeof val === 'object' && val !== null && !(val instanceof Array)
57
+ return val !== null && typeof val === 'object' && !Array.isArray(val)
58
58
  }
59
59
 
60
60
  module.exports = SharedbPlugin
@@ -6,7 +6,8 @@ const {
6
6
  finishAllTraceSpans,
7
7
  getTestSuitePath,
8
8
  getTestSuiteCommonTags,
9
- TEST_SOURCE_FILE
9
+ TEST_SOURCE_FILE,
10
+ TEST_IS_RETRY
10
11
  } = require('../../dd-trace/src/plugins/util/test')
11
12
  const { COMPONENT } = require('../../dd-trace/src/constants')
12
13
 
@@ -25,16 +26,22 @@ class VitestPlugin extends CiPlugin {
25
26
 
26
27
  this.taskToFinishTime = new WeakMap()
27
28
 
28
- this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath }) => {
29
+ this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath, isRetry }) => {
29
30
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
30
31
  const store = storage.getStore()
32
+
33
+ const extraTags = {
34
+ [TEST_SOURCE_FILE]: testSuite
35
+ }
36
+ if (isRetry) {
37
+ extraTags[TEST_IS_RETRY] = 'true'
38
+ }
39
+
31
40
  const span = this.startTestSpan(
32
41
  testName,
33
42
  testSuite,
34
43
  this.testSuiteSpan,
35
- {
36
- [TEST_SOURCE_FILE]: testSuite
37
- }
44
+ extraTags
38
45
  )
39
46
 
40
47
  this.enter(span, store)
@@ -73,7 +80,11 @@ class VitestPlugin extends CiPlugin {
73
80
  if (error) {
74
81
  span.setTag('error', error)
75
82
  }
76
- span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
83
+ if (duration) {
84
+ span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
85
+ } else {
86
+ span.finish() // retries will not have a duration
87
+ }
77
88
  finishAllTraceSpans(span)
78
89
  }
79
90
  })
@@ -4,7 +4,7 @@ const { MEASURED } = require('../../../ext/tags')
4
4
 
5
5
  module.exports = {
6
6
  sample (span, measured, measuredByDefault) {
7
- if (typeof measured === 'object') {
7
+ if (measured !== null && typeof measured === 'object') {
8
8
  this.sample(span, measured[span.context()._name], measuredByDefault)
9
9
  } else if (measured !== undefined) {
10
10
  span.setTag(MEASURED, !!measured)
@@ -13,7 +13,7 @@ const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose', 'mq
13
13
  const MONGODB_NOSQL_SECURE_MARK = getNextSecureMark()
14
14
 
15
15
  function iterateObjectStrings (target, fn, levelKeys = [], depth = 20, visited = new Set()) {
16
- if (target && typeof target === 'object') {
16
+ if (target !== null && typeof target === 'object') {
17
17
  Object.keys(target).forEach((key) => {
18
18
  const nextLevelKeys = [...levelKeys, key]
19
19
  const val = target[key]
@@ -45,7 +45,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
45
45
  this.addSub(
46
46
  { channelName: 'apm:express:middleware:next', tag: HTTP_REQUEST_BODY },
47
47
  ({ req }) => {
48
- if (req && req.body && typeof req.body === 'object') {
48
+ if (req && req.body !== null && typeof req.body === 'object') {
49
49
  const iastContext = getIastContext(storage.getStore())
50
50
  if (iastContext && iastContext.body !== req.body) {
51
51
  this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
@@ -63,7 +63,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
63
63
  this.addSub(
64
64
  { channelName: 'datadog:express:process_params:start', tag: HTTP_REQUEST_PATH_PARAM },
65
65
  ({ req }) => {
66
- if (req && req.params && typeof req.params === 'object') {
66
+ if (req && req.params !== null && typeof req.params === 'object') {
67
67
  this._taintTrackingHandler(HTTP_REQUEST_PATH_PARAM, req, 'params')
68
68
  }
69
69
  }
@@ -27,14 +27,14 @@ class KafkaConsumerIastPlugin extends SourceIastPlugin {
27
27
  if (iastContext && message) {
28
28
  const { key, value } = message
29
29
 
30
- if (key && typeof key === 'object') {
30
+ if (key !== null && typeof key === 'object') {
31
31
  shimmer.wrap(key, 'toString',
32
32
  toString => this.getToStringWrap(toString, iastContext, KAFKA_MESSAGE_KEY))
33
33
 
34
34
  newTaintedObject(iastContext, key, undefined, KAFKA_MESSAGE_KEY)
35
35
  }
36
36
 
37
- if (value && typeof value === 'object') {
37
+ if (value !== null && typeof value === 'object') {
38
38
  shimmer.wrap(value, 'toString',
39
39
  toString => this.getToStringWrap(toString, iastContext, KAFKA_MESSAGE_VALUE))
40
40
 
@@ -43,6 +43,8 @@ class VulnerabilityFormatter {
43
43
  const valueParts = []
44
44
  let fromIndex = 0
45
45
 
46
+ if (evidence.value == null) return { valueParts }
47
+
46
48
  if (typeof evidence.value === 'object' && evidence.rangesToApply) {
47
49
  const { value, ranges } = stringifyWithRanges(evidence.value, evidence.rangesToApply)
48
50
  evidence.value = value
@@ -69,7 +71,7 @@ class VulnerabilityFormatter {
69
71
  }
70
72
 
71
73
  formatEvidence (type, evidence, sourcesIndexes, sources) {
72
- if (typeof evidence.value === 'undefined') {
74
+ if (evidence.value === undefined) {
73
75
  return undefined
74
76
  }
75
77
 
@@ -121,16 +121,16 @@ function incomingHttpEndTranslator ({ req, res }) {
121
121
  }
122
122
 
123
123
  // TODO: temporary express instrumentation, will use express plugin later
124
- if (req.params && typeof req.params === 'object') {
124
+ if (req.params !== null && typeof req.params === 'object') {
125
125
  persistent[addresses.HTTP_INCOMING_PARAMS] = req.params
126
126
  }
127
127
 
128
128
  // we need to keep this to support other cookie parsers
129
- if (req.cookies && typeof req.cookies === 'object') {
129
+ if (req.cookies !== null && typeof req.cookies === 'object') {
130
130
  persistent[addresses.HTTP_INCOMING_COOKIES] = req.cookies
131
131
  }
132
132
 
133
- if (req.query && typeof req.query === 'object') {
133
+ if (req.query !== null && typeof req.query === 'object') {
134
134
  persistent[addresses.HTTP_INCOMING_QUERY] = req.query
135
135
  }
136
136
 
@@ -13,7 +13,7 @@ const regexSdkEvent = new RegExp(SDK_USER_EVENT_PATTERN, 'i')
13
13
  function isSdkCalled (tags) {
14
14
  let called = false
15
15
 
16
- if (tags && typeof tags === 'object') {
16
+ if (tags !== null && typeof tags === 'object') {
17
17
  called = Object.entries(tags).some(([key, value]) => regexSdkEvent.test(key) && value === 'true')
18
18
  }
19
19
 
@@ -207,10 +207,6 @@ function finishRequest (req, res) {
207
207
 
208
208
  // collect some headers even when no attack is detected
209
209
  const mandatoryTags = filterHeaders(req.headers, REQUEST_HEADERS_MAP)
210
- const ua = mandatoryTags['http.request.headers.user-agent']
211
- if (ua) {
212
- mandatoryTags['http.useragent'] = ua
213
- }
214
210
  rootSpan.addTags(mandatoryTags)
215
211
 
216
212
  const tags = rootSpan.context()._tags
@@ -25,7 +25,7 @@ class WAFContextWrapper {
25
25
  const inputs = {}
26
26
  const newAddressesToSkip = new Set(this.addressesToSkip)
27
27
 
28
- if (persistent && typeof persistent === 'object') {
28
+ if (persistent !== null && typeof persistent === 'object') {
29
29
  // TODO: possible optimization: only send params that haven't already been sent with same value to this wafContext
30
30
  for (const key of Object.keys(persistent)) {
31
31
  // TODO: requiredAddresses is no longer used due to processor addresses are not included in the list. Check on