dd-trace 5.36.0 → 5.37.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 (65) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/index.d.ts +5 -0
  3. package/loader-hook.mjs +0 -4
  4. package/package.json +14 -13
  5. package/packages/datadog-instrumentations/src/cucumber.js +54 -1
  6. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  7. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  8. package/packages/datadog-instrumentations/src/jest.js +103 -5
  9. package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
  11. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
  12. package/packages/datadog-instrumentations/src/mysql2.js +3 -3
  13. package/packages/datadog-instrumentations/src/openai.js +8 -0
  14. package/packages/datadog-instrumentations/src/playwright.js +70 -22
  15. package/packages/datadog-instrumentations/src/vitest.js +60 -6
  16. package/packages/datadog-plugin-cucumber/src/index.js +20 -3
  17. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +67 -7
  18. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  19. package/packages/datadog-plugin-graphql/src/utils.js +8 -1
  20. package/packages/datadog-plugin-jest/src/index.js +12 -2
  21. package/packages/datadog-plugin-mocha/src/index.js +22 -3
  22. package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
  23. package/packages/datadog-plugin-openai/src/tracing.js +1 -2
  24. package/packages/datadog-plugin-playwright/src/index.js +31 -5
  25. package/packages/datadog-plugin-vitest/src/index.js +25 -1
  26. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -6
  27. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
  28. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +11 -24
  29. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +2 -6
  30. package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
  31. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
  32. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +22 -2
  33. package/packages/dd-trace/src/appsec/iast/index.js +2 -0
  34. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
  35. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
  36. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
  37. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
  39. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
  40. package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
  41. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
  42. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
  43. package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -4
  44. package/packages/dd-trace/src/appsec/rasp/lfi.js +2 -2
  45. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
  46. package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -2
  47. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -49
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
  49. package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
  50. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
  51. package/packages/dd-trace/src/config.js +16 -3
  52. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  53. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
  54. package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
  55. package/packages/dd-trace/src/iitm.js +2 -2
  56. package/packages/dd-trace/src/llmobs/tagger.js +12 -2
  57. package/packages/dd-trace/src/opentracing/span.js +2 -2
  58. package/packages/dd-trace/src/plugins/ci_plugin.js +16 -3
  59. package/packages/dd-trace/src/plugins/database.js +14 -4
  60. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
  61. package/packages/dd-trace/src/plugins/util/test.js +6 -4
  62. package/packages/dd-trace/src/proxy.js +5 -1
  63. package/packages/dd-trace/src/ritm.js +2 -1
  64. package/packages/dd-trace/src/spanleak.js +0 -1
  65. package/packages/memwatch/package.json +0 -9
@@ -28,7 +28,7 @@ module.exports = class DdTraceApiPlugin extends Plugin {
28
28
  })
29
29
 
30
30
  const handleEvent = (name) => {
31
- const counter = apiMetrics.count('dd_trace_api.called', [
31
+ const counter = apiMetrics.count('public_api.called', [
32
32
  `name:${name.replaceAll(':', '.')}`,
33
33
  'api_version:v1',
34
34
  injectionEnabledTag
@@ -74,8 +74,6 @@ module.exports = class DdTraceApiPlugin extends Plugin {
74
74
  const proxyVal = proxy()
75
75
  objectMap.set(proxyVal, ret.value)
76
76
  ret.value = proxyVal
77
- } else if (ret.value && typeof ret.value === 'object') {
78
- throw new TypeError(`Objects need proxies when returned via API (${name})`)
79
77
  }
80
78
  } catch (e) {
81
79
  ret.error = e
@@ -27,7 +27,14 @@ function extractErrorIntoSpanEvent (config, span, exc) {
27
27
  if (config.graphqlErrorExtensions) {
28
28
  for (const ext of config.graphqlErrorExtensions) {
29
29
  if (exc.extensions?.[ext]) {
30
- attributes[`extensions.${ext}`] = exc.extensions[ext].toString()
30
+ const value = exc.extensions[ext]
31
+
32
+ // We should only stringify the value if it is not of type number or boolean
33
+ if (typeof value === 'number' || typeof value === 'boolean') {
34
+ attributes[`extensions.${ext}`] = value
35
+ } else {
36
+ attributes[`extensions.${ext}`] = String(value)
37
+ }
31
38
  }
32
39
  }
33
40
  }
@@ -24,7 +24,9 @@ const {
24
24
  TEST_IS_RUM_ACTIVE,
25
25
  TEST_BROWSER_DRIVER,
26
26
  getFormattedError,
27
- TEST_RETRY_REASON
27
+ TEST_RETRY_REASON,
28
+ TEST_MANAGEMENT_ENABLED,
29
+ TEST_MANAGEMENT_IS_QUARANTINED
28
30
  } = require('../../dd-trace/src/plugins/util/test')
29
31
  const { COMPONENT } = require('../../dd-trace/src/constants')
30
32
  const id = require('../../dd-trace/src/id')
@@ -106,6 +108,7 @@ class JestPlugin extends CiPlugin {
106
108
  error,
107
109
  isEarlyFlakeDetectionEnabled,
108
110
  isEarlyFlakeDetectionFaulty,
111
+ isQuarantinedTestsEnabled,
109
112
  onDone
110
113
  }) => {
111
114
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -137,6 +140,9 @@ class JestPlugin extends CiPlugin {
137
140
  if (isEarlyFlakeDetectionFaulty) {
138
141
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
139
142
  }
143
+ if (isQuarantinedTestsEnabled) {
144
+ this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
145
+ }
140
146
 
141
147
  this.testModuleSpan.finish()
142
148
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
@@ -166,6 +172,7 @@ class JestPlugin extends CiPlugin {
166
172
  config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
167
173
  config._ddRepositoryRoot = this.repositoryRoot
168
174
  config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
175
+ config._ddIsQuarantinedTestsEnabled = this.libraryConfig?.isQuarantinedTestsEnabled ?? false
169
176
  config._ddFlakyTestRetriesCount = this.libraryConfig?.flakyTestRetriesCount
170
177
  config._ddIsDiEnabled = this.libraryConfig?.isDiEnabled ?? false
171
178
  config._ddIsKnownTestsEnabled = this.libraryConfig?.isKnownTestsEnabled ?? false
@@ -325,12 +332,15 @@ class JestPlugin extends CiPlugin {
325
332
  this.activeTestSpan = span
326
333
  })
327
334
 
328
- this.addSub('ci:jest:test:finish', ({ status, testStartLine }) => {
335
+ this.addSub('ci:jest:test:finish', ({ status, testStartLine, isQuarantined }) => {
329
336
  const span = storage('legacy').getStore().span
330
337
  span.setTag(TEST_STATUS, status)
331
338
  if (testStartLine) {
332
339
  span.setTag(TEST_SOURCE_START, testStartLine)
333
340
  }
341
+ if (isQuarantined) {
342
+ span.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
343
+ }
334
344
 
335
345
  const spanTags = span.context()._tags
336
346
  this.telemetry.ciVisEvent(
@@ -31,7 +31,9 @@ const {
31
31
  MOCHA_IS_PARALLEL,
32
32
  TEST_IS_RUM_ACTIVE,
33
33
  TEST_BROWSER_DRIVER,
34
- TEST_RETRY_REASON
34
+ TEST_RETRY_REASON,
35
+ TEST_MANAGEMENT_ENABLED,
36
+ TEST_MANAGEMENT_IS_QUARANTINED
35
37
  } = require('../../dd-trace/src/plugins/util/test')
36
38
  const { COMPONENT } = require('../../dd-trace/src/constants')
37
39
  const {
@@ -48,6 +50,8 @@ const {
48
50
  const id = require('../../dd-trace/src/id')
49
51
  const log = require('../../dd-trace/src/log')
50
52
 
53
+ const BREAKPOINT_SET_GRACE_PERIOD_MS = 200
54
+
51
55
  function getTestSuiteLevelVisibilityTags (testSuiteSpan) {
52
56
  const testSuiteSpanContext = testSuiteSpan.context()
53
57
  const suiteTags = {
@@ -279,7 +283,12 @@ class MochaPlugin extends CiPlugin {
279
283
  this.runningTestProbe = { file, line }
280
284
  this.testErrorStackIndex = stackIndex
281
285
  test._ddShouldWaitForHitProbe = true
282
- // TODO: we're not waiting for setProbePromise to be resolved, so there might be race conditions
286
+ const waitUntil = Date.now() + BREAKPOINT_SET_GRACE_PERIOD_MS
287
+ while (Date.now() < waitUntil) {
288
+ // TODO: To avoid a race condition, we should wait until `probeInformation.setProbePromise` has resolved.
289
+ // However, Mocha doesn't have a mechanism for waiting asyncrounously here, so for now, we'll have to
290
+ // fall back to a fixed syncronous delay.
291
+ }
283
292
  }
284
293
  }
285
294
 
@@ -302,6 +311,7 @@ class MochaPlugin extends CiPlugin {
302
311
  error,
303
312
  isEarlyFlakeDetectionEnabled,
304
313
  isEarlyFlakeDetectionFaulty,
314
+ isQuarantinedTestsEnabled,
305
315
  isParallel
306
316
  }) => {
307
317
  if (this.testSessionSpan) {
@@ -318,6 +328,10 @@ class MochaPlugin extends CiPlugin {
318
328
  this.testSessionSpan.setTag(MOCHA_IS_PARALLEL, 'true')
319
329
  }
320
330
 
331
+ if (isQuarantinedTestsEnabled) {
332
+ this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
333
+ }
334
+
321
335
  addIntelligentTestRunnerSpanTags(
322
336
  this.testSessionSpan,
323
337
  this.testModuleSpan,
@@ -390,7 +404,8 @@ class MochaPlugin extends CiPlugin {
390
404
  isNew,
391
405
  isEfdRetry,
392
406
  testStartLine,
393
- isParallel
407
+ isParallel,
408
+ isQuarantined
394
409
  } = testInfo
395
410
 
396
411
  const testName = removeEfdStringFromTestName(testInfo.testName)
@@ -409,6 +424,10 @@ class MochaPlugin extends CiPlugin {
409
424
  extraTags[MOCHA_IS_PARALLEL] = 'true'
410
425
  }
411
426
 
427
+ if (isQuarantined) {
428
+ extraTags[TEST_MANAGEMENT_IS_QUARANTINED] = 'true'
429
+ }
430
+
412
431
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
413
432
  const testSuiteSpan = this._testSuites.get(testSuite)
414
433
 
@@ -11,8 +11,9 @@ class MongodbCorePlugin extends DatabasePlugin {
11
11
  start ({ ns, ops, options = {}, name }) {
12
12
  const query = getQuery(ops)
13
13
  const resource = truncate(getResource(this, ns, query, name))
14
- this.startSpan(this.operationName(), {
15
- service: this.serviceName({ pluginConfig: this.config }),
14
+ const service = this.serviceName({ pluginConfig: this.config })
15
+ const span = this.startSpan(this.operationName(), {
16
+ service,
16
17
  resource,
17
18
  type: 'mongodb',
18
19
  kind: 'client',
@@ -24,6 +25,7 @@ class MongodbCorePlugin extends DatabasePlugin {
24
25
  'out.port': options.port
25
26
  }
26
27
  })
28
+ ops = this.injectDbmCommand(span, ops, service)
27
29
  }
28
30
 
29
31
  getPeerService (tags) {
@@ -34,6 +36,30 @@ class MongodbCorePlugin extends DatabasePlugin {
34
36
  }
35
37
  return super.getPeerService(tags)
36
38
  }
39
+
40
+ injectDbmCommand (span, command, serviceName) {
41
+ const dbmTraceComment = this.createDbmComment(span, serviceName)
42
+
43
+ if (!dbmTraceComment) {
44
+ return command
45
+ }
46
+
47
+ // create a copy of the command to avoid mutating the original
48
+ const dbmTracedCommand = { ...command }
49
+
50
+ if (dbmTracedCommand.comment) {
51
+ // if the command already has a comment, append the dbm trace comment
52
+ if (typeof dbmTracedCommand.comment === 'string') {
53
+ dbmTracedCommand.comment += `,${dbmTraceComment}`
54
+ } else if (Array.isArray(dbmTracedCommand.comment)) {
55
+ dbmTracedCommand.comment.push(dbmTraceComment)
56
+ } // do nothing if the comment is not a string or an array
57
+ } else {
58
+ dbmTracedCommand.comment = dbmTraceComment
59
+ }
60
+
61
+ return dbmTracedCommand
62
+ }
37
63
  }
38
64
 
39
65
  function sanitizeBigInt (data) {
@@ -15,7 +15,6 @@ let normalize
15
15
 
16
16
  function safeRequire (path) {
17
17
  try {
18
- // eslint-disable-next-line import/no-extraneous-dependencies
19
18
  return require(path)
20
19
  } catch {
21
20
  return null
@@ -796,7 +795,7 @@ function truncateApiKey (apiKey) {
796
795
 
797
796
  function tagChatCompletionRequestContent (contents, messageIdx, tags) {
798
797
  if (typeof contents === 'string') {
799
- tags[`openai.request.messages.${messageIdx}.content`] = contents
798
+ tags[`openai.request.messages.${messageIdx}.content`] = normalize(contents)
800
799
  } else if (Array.isArray(contents)) {
801
800
  // content can also be an array of objects
802
801
  // which represent text input or image url
@@ -11,12 +11,15 @@ const {
11
11
  TEST_SOURCE_START,
12
12
  TEST_CODE_OWNERS,
13
13
  TEST_SOURCE_FILE,
14
- TEST_CONFIGURATION_BROWSER_NAME,
14
+ TEST_PARAMETERS,
15
15
  TEST_IS_NEW,
16
16
  TEST_IS_RETRY,
17
17
  TEST_EARLY_FLAKE_ENABLED,
18
18
  TELEMETRY_TEST_SESSION,
19
- TEST_RETRY_REASON
19
+ TEST_RETRY_REASON,
20
+ TEST_MANAGEMENT_IS_QUARANTINED,
21
+ TEST_MANAGEMENT_ENABLED,
22
+ TEST_BROWSER_NAME
20
23
  } = require('../../dd-trace/src/plugins/util/test')
21
24
  const { RESOURCE_NAME } = require('../../../ext/tags')
22
25
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -38,7 +41,12 @@ class PlaywrightPlugin extends CiPlugin {
38
41
  this.numFailedTests = 0
39
42
  this.numFailedSuites = 0
40
43
 
41
- this.addSub('ci:playwright:session:finish', ({ status, isEarlyFlakeDetectionEnabled, onDone }) => {
44
+ this.addSub('ci:playwright:session:finish', ({
45
+ status,
46
+ isEarlyFlakeDetectionEnabled,
47
+ isQuarantinedTestsEnabled,
48
+ onDone
49
+ }) => {
42
50
  this.testModuleSpan.setTag(TEST_STATUS, status)
43
51
  this.testSessionSpan.setTag(TEST_STATUS, status)
44
52
 
@@ -56,6 +64,10 @@ class PlaywrightPlugin extends CiPlugin {
56
64
  this.testSessionSpan.setTag('error', error)
57
65
  }
58
66
 
67
+ if (isQuarantinedTestsEnabled) {
68
+ this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
69
+ }
70
+
59
71
  this.testModuleSpan.finish()
60
72
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
61
73
  this.testSessionSpan.finish()
@@ -128,7 +140,16 @@ class PlaywrightPlugin extends CiPlugin {
128
140
 
129
141
  this.enter(span, store)
130
142
  })
131
- this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry, isRetry }) => {
143
+ this.addSub('ci:playwright:test:finish', ({
144
+ testStatus,
145
+ steps,
146
+ error,
147
+ extraTags,
148
+ isNew,
149
+ isEfdRetry,
150
+ isRetry,
151
+ isQuarantined
152
+ }) => {
132
153
  const store = storage('legacy').getStore()
133
154
  const span = store && store.span
134
155
  if (!span) return
@@ -151,6 +172,9 @@ class PlaywrightPlugin extends CiPlugin {
151
172
  if (isRetry) {
152
173
  span.setTag(TEST_IS_RETRY, 'true')
153
174
  }
175
+ if (isQuarantined) {
176
+ span.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
177
+ }
154
178
 
155
179
  steps.forEach(step => {
156
180
  const stepStartTime = step.startTime.getTime()
@@ -202,7 +226,9 @@ class PlaywrightPlugin extends CiPlugin {
202
226
  extraTags[TEST_SOURCE_FILE] = testSourceFile || testSuite
203
227
  }
204
228
  if (browserName) {
205
- extraTags[TEST_CONFIGURATION_BROWSER_NAME] = browserName
229
+ // Added as parameter too because it should affect the test fingerprint
230
+ extraTags[TEST_PARAMETERS] = JSON.stringify({ arguments: { browser: browserName }, metadata: {} })
231
+ extraTags[TEST_BROWSER_NAME] = browserName
206
232
  }
207
233
 
208
234
  return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
@@ -18,7 +18,9 @@ const {
18
18
  TEST_IS_NEW,
19
19
  TEST_EARLY_FLAKE_ENABLED,
20
20
  TEST_EARLY_FLAKE_ABORT_REASON,
21
- TEST_RETRY_REASON
21
+ TEST_RETRY_REASON,
22
+ TEST_MANAGEMENT_ENABLED,
23
+ TEST_MANAGEMENT_IS_QUARANTINED
22
24
  } = require('../../dd-trace/src/plugins/util/test')
23
25
  const { COMPONENT } = require('../../dd-trace/src/constants')
24
26
  const {
@@ -48,6 +50,20 @@ class VitestPlugin extends CiPlugin {
48
50
  onDone(!testsForThisTestSuite.includes(testName))
49
51
  })
50
52
 
53
+ this.addSub('ci:vitest:test:is-quarantined', ({ quarantinedTests, testSuiteAbsolutePath, testName, onDone }) => {
54
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
55
+ const isQuarantined = quarantinedTests
56
+ ?.vitest
57
+ ?.suites
58
+ ?.[testSuite]
59
+ ?.tests
60
+ ?.[testName]
61
+ ?.properties
62
+ ?.quarantined
63
+
64
+ onDone(isQuarantined ?? false)
65
+ })
66
+
51
67
  this.addSub('ci:vitest:is-early-flake-detection-faulty', ({
52
68
  knownTests,
53
69
  testFilepaths,
@@ -66,6 +82,7 @@ class VitestPlugin extends CiPlugin {
66
82
  testSuiteAbsolutePath,
67
83
  isRetry,
68
84
  isNew,
85
+ isQuarantined,
69
86
  mightHitProbe,
70
87
  isRetryReasonEfd
71
88
  }) => {
@@ -84,6 +101,9 @@ class VitestPlugin extends CiPlugin {
84
101
  if (isRetryReasonEfd) {
85
102
  extraTags[TEST_RETRY_REASON] = 'efd'
86
103
  }
104
+ if (isQuarantined) {
105
+ extraTags[TEST_MANAGEMENT_IS_QUARANTINED] = 'true'
106
+ }
87
107
 
88
108
  const span = this.startTestSpan(
89
109
  testName,
@@ -257,6 +277,7 @@ class VitestPlugin extends CiPlugin {
257
277
  testCodeCoverageLinesTotal,
258
278
  isEarlyFlakeDetectionEnabled,
259
279
  isEarlyFlakeDetectionFaulty,
280
+ isQuarantinedTestsEnabled,
260
281
  onFinish
261
282
  }) => {
262
283
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -275,6 +296,9 @@ class VitestPlugin extends CiPlugin {
275
296
  if (isEarlyFlakeDetectionFaulty) {
276
297
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
277
298
  }
299
+ if (isQuarantinedTestsEnabled) {
300
+ this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
301
+ }
278
302
  this.testModuleSpan.finish()
279
303
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
280
304
  this.testSessionSpan.finish()
@@ -1,12 +1,12 @@
1
1
  'use strict'
2
2
 
3
- const InjectionAnalyzer = require('./injection-analyzer')
4
3
  const { CODE_INJECTION } = require('../vulnerabilities')
4
+ const StoredInjectionAnalyzer = require('./stored-injection-analyzer')
5
5
  const { INSTRUMENTED_SINK } = require('../telemetry/iast-metric')
6
6
  const { storage } = require('../../../../../datadog-core')
7
7
  const { getIastContext } = require('../iast-context')
8
8
 
9
- class CodeInjectionAnalyzer extends InjectionAnalyzer {
9
+ class CodeInjectionAnalyzer extends StoredInjectionAnalyzer {
10
10
  constructor () {
11
11
  super(CODE_INJECTION)
12
12
  this.evalInstrumentedInc = false
@@ -31,10 +31,6 @@ class CodeInjectionAnalyzer extends InjectionAnalyzer {
31
31
  this.addSub('datadog:vm:run-script:start', ({ code }) => this.analyze(code))
32
32
  this.addSub('datadog:vm:source-text-module:start', ({ code }) => this.analyze(code))
33
33
  }
34
-
35
- _areRangesVulnerable () {
36
- return true
37
- }
38
34
  }
39
35
 
40
36
  module.exports = new CodeInjectionAnalyzer()
@@ -5,8 +5,13 @@ const { SQL_ROW_VALUE } = require('../taint-tracking/source-types')
5
5
 
6
6
  class InjectionAnalyzer extends Analyzer {
7
7
  _isVulnerable (value, iastContext) {
8
- const ranges = value && getRanges(iastContext, value)
8
+ let ranges = value && getRanges(iastContext, value)
9
9
  if (ranges?.length > 0) {
10
+ ranges = this._filterSecureRanges(ranges)
11
+ if (!ranges?.length) {
12
+ this._incrementSuppressedMetric(iastContext)
13
+ }
14
+
10
15
  return this._areRangesVulnerable(ranges)
11
16
  }
12
17
 
@@ -21,6 +26,15 @@ class InjectionAnalyzer extends Analyzer {
21
26
  _areRangesVulnerable (ranges) {
22
27
  return ranges?.some(range => range.iinfo.type !== SQL_ROW_VALUE)
23
28
  }
29
+
30
+ _filterSecureRanges (ranges) {
31
+ return ranges?.filter(range => !this._isRangeSecure(range))
32
+ }
33
+
34
+ _isRangeSecure (range) {
35
+ const { secureMarks } = range
36
+ return (secureMarks & this._secureMark) === this._secureMark
37
+ }
24
38
  }
25
39
 
26
40
  module.exports = InjectionAnalyzer
@@ -4,29 +4,13 @@ const InjectionAnalyzer = require('./injection-analyzer')
4
4
  const { NOSQL_MONGODB_INJECTION } = require('../vulnerabilities')
5
5
  const { getRanges, addSecureMark } = require('../taint-tracking/operations')
6
6
  const { getNodeModulesPaths } = require('../path-line')
7
- const { getNextSecureMark } = require('../taint-tracking/secure-marks-generator')
8
7
  const { storage } = require('../../../../../datadog-core')
9
8
  const { getIastContext } = require('../iast-context')
10
9
  const { HTTP_REQUEST_PARAMETER, HTTP_REQUEST_BODY } = require('../taint-tracking/source-types')
11
10
 
12
11
  const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose', 'mquery')
13
- const MONGODB_NOSQL_SECURE_MARK = getNextSecureMark()
14
-
15
- function iterateObjectStrings (target, fn, levelKeys = [], depth = 20, visited = new Set()) {
16
- if (target !== null && typeof target === 'object') {
17
- Object.keys(target).forEach((key) => {
18
- const nextLevelKeys = [...levelKeys, key]
19
- const val = target[key]
20
-
21
- if (typeof val === 'string') {
22
- fn(val, nextLevelKeys, target, key)
23
- } else if (depth > 0 && !visited.has(val)) {
24
- iterateObjectStrings(val, fn, nextLevelKeys, depth - 1, visited)
25
- visited.add(val)
26
- }
27
- })
28
- }
29
- }
12
+ const { NOSQL_MONGODB_INJECTION_MARK } = require('../taint-tracking/secure-marks')
13
+ const { iterateObjectStrings } = require('../utils')
30
14
 
31
15
  class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
32
16
  constructor () {
@@ -88,7 +72,7 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
88
72
  const currentLevelKey = levelKeys[i]
89
73
 
90
74
  if (i === levelsLength - 1) {
91
- parentObj[currentLevelKey] = addSecureMark(iastContext, value, MONGODB_NOSQL_SECURE_MARK)
75
+ parentObj[currentLevelKey] = addSecureMark(iastContext, value, NOSQL_MONGODB_INJECTION_MARK)
92
76
  } else {
93
77
  parentObj = parentObj[currentLevelKey]
94
78
  }
@@ -106,7 +90,7 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
106
90
  if (iastContext) { // do nothing if we are not in an iast request
107
91
  iterateObjectStrings(sanitizedObject, function (value, levelKeys, parent, lastKey) {
108
92
  try {
109
- parent[lastKey] = addSecureMark(iastContext, value, MONGODB_NOSQL_SECURE_MARK)
93
+ parent[lastKey] = addSecureMark(iastContext, value, NOSQL_MONGODB_INJECTION_MARK)
110
94
  } catch {
111
95
  // if it is a readonly property, do nothing
112
96
  }
@@ -121,8 +105,7 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
121
105
 
122
106
  _isVulnerableRange (range) {
123
107
  const rangeType = range?.iinfo?.type
124
- const isVulnerableType = rangeType === HTTP_REQUEST_PARAMETER || rangeType === HTTP_REQUEST_BODY
125
- return isVulnerableType && (range.secureMarks & MONGODB_NOSQL_SECURE_MARK) !== MONGODB_NOSQL_SECURE_MARK
108
+ return rangeType === HTTP_REQUEST_PARAMETER || rangeType === HTTP_REQUEST_BODY
126
109
  }
127
110
 
128
111
  _isVulnerable (value, iastContext) {
@@ -137,10 +120,15 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
137
120
  const allRanges = []
138
121
 
139
122
  iterateObjectStrings(value.filter, (val, nextLevelKeys) => {
140
- const ranges = getRanges(iastContext, val)
123
+ let ranges = getRanges(iastContext, val)
141
124
  if (ranges?.length) {
142
125
  const filteredRanges = []
143
126
 
127
+ ranges = this._filterSecureRanges(ranges)
128
+ if (!ranges.length) {
129
+ this._incrementSuppressedMetric(iastContext)
130
+ }
131
+
144
132
  for (const range of ranges) {
145
133
  if (this._isVulnerableRange(range)) {
146
134
  isVulnerable = true
@@ -175,4 +163,3 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
175
163
  }
176
164
 
177
165
  module.exports = new NosqlInjectionMongodbAnalyzer()
178
- module.exports.MONGODB_NOSQL_SECURE_MARK = MONGODB_NOSQL_SECURE_MARK
@@ -1,14 +1,14 @@
1
1
  'use strict'
2
2
 
3
- const InjectionAnalyzer = require('./injection-analyzer')
4
3
  const { SQL_INJECTION } = require('../vulnerabilities')
5
4
  const { getRanges } = require('../taint-tracking/operations')
6
5
  const { storage } = require('../../../../../datadog-core')
7
6
  const { getNodeModulesPaths } = require('../path-line')
7
+ const StoredInjectionAnalyzer = require('./stored-injection-analyzer')
8
8
 
9
9
  const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool', 'knex')
10
10
 
11
- class SqlInjectionAnalyzer extends InjectionAnalyzer {
11
+ class SqlInjectionAnalyzer extends StoredInjectionAnalyzer {
12
12
  constructor () {
13
13
  super(SQL_INJECTION)
14
14
  }
@@ -82,10 +82,6 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
82
82
  return knexDialect.toUpperCase()
83
83
  }
84
84
  }
85
-
86
- _areRangesVulnerable () {
87
- return true
88
- }
89
85
  }
90
86
 
91
87
  module.exports = new SqlInjectionAnalyzer()
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const InjectionAnalyzer = require('./injection-analyzer')
4
+
5
+ class StoredInjectionAnalyzer extends InjectionAnalyzer {
6
+ _areRangesVulnerable (ranges) {
7
+ return ranges?.length > 0
8
+ }
9
+ }
10
+
11
+ module.exports = StoredInjectionAnalyzer
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const InjectionAnalyzer = require('./injection-analyzer')
4
3
  const { TEMPLATE_INJECTION } = require('../vulnerabilities')
4
+ const StoredInjectionAnalyzer = require('./stored-injection-analyzer')
5
5
 
6
- class TemplateInjectionAnalyzer extends InjectionAnalyzer {
6
+ class TemplateInjectionAnalyzer extends StoredInjectionAnalyzer {
7
7
  constructor () {
8
8
  super(TEMPLATE_INJECTION)
9
9
  }
@@ -13,10 +13,6 @@ class TemplateInjectionAnalyzer extends InjectionAnalyzer {
13
13
  this.addSub('datadog:handlebars:register-partial:start', ({ partial }) => this.analyze(partial))
14
14
  this.addSub('datadog:pug:compile:start', ({ source }) => this.analyze(source))
15
15
  }
16
-
17
- _areRangesVulnerable () {
18
- return true
19
- }
20
16
  }
21
17
 
22
18
  module.exports = new TemplateInjectionAnalyzer()
@@ -10,11 +10,14 @@ const {
10
10
  getVulnerabilityCallSiteFrames,
11
11
  replaceCallSiteFromSourceMap
12
12
  } = require('../vulnerability-reporter')
13
+ const { getMarkFromVulnerabilityType } = require('../taint-tracking/secure-marks')
14
+ const { SUPPRESSED_VULNERABILITIES } = require('../telemetry/iast-metric')
13
15
 
14
16
  class Analyzer extends SinkIastPlugin {
15
17
  constructor (type) {
16
18
  super()
17
19
  this._type = type
20
+ this._secureMark = getMarkFromVulnerabilityType(type)
18
21
  }
19
22
 
20
23
  _isVulnerable (value, context) {
@@ -75,11 +78,17 @@ class Analyzer extends SinkIastPlugin {
75
78
  if (locationFromSourceMap?.path) {
76
79
  originalLocation.path = locationFromSourceMap.path
77
80
  }
81
+
78
82
  if (locationFromSourceMap?.line) {
79
83
  originalLocation.line = locationFromSourceMap.line
80
84
  }
81
- if (locationFromSourceMap?.column) {
82
- originalLocation.column = locationFromSourceMap.column
85
+
86
+ if (location?.class_name) {
87
+ originalLocation.class = location.class_name
88
+ }
89
+
90
+ if (location?.function) {
91
+ originalLocation.method = location.function
83
92
  }
84
93
 
85
94
  return originalLocation
@@ -149,6 +158,17 @@ class Analyzer extends SinkIastPlugin {
149
158
  return hash
150
159
  }
151
160
 
161
+ _getSuppressedMetricTag () {
162
+ if (!this._suppressedMetricTag) {
163
+ this._suppressedMetricTag = SUPPRESSED_VULNERABILITIES.formatTags(this._type)[0]
164
+ }
165
+ return this._suppressedMetricTag
166
+ }
167
+
168
+ _incrementSuppressedMetric (iastContext) {
169
+ SUPPRESSED_VULNERABILITIES.inc(iastContext, this._getSuppressedMetricTag())
170
+ }
171
+
152
172
  addSub (iastSubOrChannelName, handler) {
153
173
  const iastSub = typeof iastSubOrChannelName === 'string'
154
174
  ? { channelName: iastSubOrChannelName }
@@ -15,6 +15,7 @@ const {
15
15
  const { IAST_ENABLED_TAG_KEY } = require('./tags')
16
16
  const iastTelemetry = require('./telemetry')
17
17
  const { enable: enableFsPlugin, disable: disableFsPlugin, IAST_MODULE } = require('../rasp/fs-plugin')
18
+ const securityControls = require('./security-controls')
18
19
 
19
20
  // TODO Change to `apm:http:server:request:[start|close]` when the subscription
20
21
  // order of the callbacks can be enforce
@@ -35,6 +36,7 @@ function enable (config, _tracer) {
35
36
  requestClose.subscribe(onIncomingHttpRequestEnd)
36
37
  overheadController.configure(config.iast)
37
38
  overheadController.startGlobalContext()
39
+ securityControls.configure(config.iast)
38
40
  vulnerabilityReporter.start(config, _tracer)
39
41
 
40
42
  isEnabled = true