dd-trace 4.26.0 → 4.28.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 (86) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +1 -32
  3. package/ci/init.js +1 -4
  4. package/index.d.ts +21 -0
  5. package/package.json +7 -6
  6. package/packages/datadog-instrumentations/src/amqplib.js +1 -1
  7. package/packages/datadog-instrumentations/src/child_process.js +150 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +12 -12
  9. package/packages/datadog-instrumentations/src/express.js +20 -0
  10. package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
  12. package/packages/datadog-instrumentations/src/jest.js +149 -11
  13. package/packages/datadog-instrumentations/src/mocha.js +142 -16
  14. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  15. package/packages/datadog-instrumentations/src/next.js +17 -3
  16. package/packages/datadog-instrumentations/src/playwright.js +41 -9
  17. package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
  18. package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +107 -1
  20. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  21. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  22. package/packages/datadog-plugin-cucumber/src/index.js +16 -11
  23. package/packages/datadog-plugin-cypress/src/plugin.js +52 -23
  24. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  25. package/packages/datadog-plugin-http/src/client.js +1 -1
  26. package/packages/datadog-plugin-jest/src/index.js +43 -6
  27. package/packages/datadog-plugin-kafkajs/src/consumer.js +16 -0
  28. package/packages/datadog-plugin-mocha/src/index.js +47 -17
  29. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  30. package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
  31. package/packages/datadog-plugin-rhea/src/producer.js +11 -0
  32. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  33. package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
  34. package/packages/dd-trace/src/appsec/channels.js +2 -1
  35. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  36. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  37. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  38. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  39. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  40. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +12 -1
  41. package/packages/dd-trace/src/appsec/iast/index.js +4 -4
  42. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  45. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  46. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  47. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  48. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  49. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  50. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +29 -2
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  52. package/packages/dd-trace/src/appsec/index.js +17 -2
  53. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  54. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  55. package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
  56. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
  57. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  58. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  59. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
  60. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
  62. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
  63. package/packages/dd-trace/src/config.js +22 -9
  64. package/packages/dd-trace/src/datastreams/processor.js +6 -0
  65. package/packages/dd-trace/src/datastreams/writer.js +2 -5
  66. package/packages/dd-trace/src/dogstatsd.js +3 -5
  67. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
  68. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  69. package/packages/dd-trace/src/format.js +25 -1
  70. package/packages/dd-trace/src/noop/span.js +1 -0
  71. package/packages/dd-trace/src/opentelemetry/span.js +9 -2
  72. package/packages/dd-trace/src/opentracing/span.js +38 -0
  73. package/packages/dd-trace/src/opentracing/span_context.js +12 -6
  74. package/packages/dd-trace/src/opentracing/tracer.js +2 -1
  75. package/packages/dd-trace/src/plugins/ci_plugin.js +25 -9
  76. package/packages/dd-trace/src/plugins/index.js +1 -0
  77. package/packages/dd-trace/src/plugins/util/git.js +6 -0
  78. package/packages/dd-trace/src/plugins/util/test.js +53 -8
  79. package/packages/dd-trace/src/profiling/config.js +22 -22
  80. package/packages/dd-trace/src/proxy.js +31 -23
  81. package/packages/dd-trace/src/span_processor.js +5 -1
  82. package/packages/dd-trace/src/telemetry/index.js +6 -0
  83. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  84. package/packages/dd-trace/src/telemetry/send-data.js +0 -3
  85. package/packages/datadog-instrumentations/src/child-process.js +0 -29
  86. package/packages/dd-trace/src/plugins/util/exec.js +0 -34
@@ -4,8 +4,6 @@ const InjectionAnalyzer = require('./injection-analyzer')
4
4
  const { SQL_INJECTION } = require('../vulnerabilities')
5
5
  const { getRanges } = require('../taint-tracking/operations')
6
6
  const { storage } = require('../../../../../datadog-core')
7
- const { getIastContext } = require('../iast-context')
8
- const { addVulnerability } = require('../vulnerability-reporter')
9
7
  const { getNodeModulesPaths } = require('../path-line')
10
8
 
11
9
  const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool', 'knex')
@@ -16,9 +14,9 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
16
14
  }
17
15
 
18
16
  onConfigure () {
19
- this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
20
- this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
21
- this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, 'POSTGRES'))
17
+ this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, undefined, 'MYSQL'))
18
+ this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, undefined, 'MYSQL'))
19
+ this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, undefined, 'POSTGRES'))
22
20
 
23
21
  this.addSub(
24
22
  'datadog:sequelize:query:start',
@@ -42,7 +40,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
42
40
  getStoreAndAnalyze (query, dialect) {
43
41
  const parentStore = storage.getStore()
44
42
  if (parentStore) {
45
- this.analyze(query, dialect, parentStore)
43
+ this.analyze(query, parentStore, dialect)
46
44
 
47
45
  storage.enterWith({ ...parentStore, sqlAnalyzed: true, sqlParentStore: parentStore })
48
46
  }
@@ -60,29 +58,10 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
60
58
  return { value, ranges, dialect }
61
59
  }
62
60
 
63
- analyze (value, dialect, store = storage.getStore()) {
61
+ analyze (value, store, dialect) {
62
+ store = store || storage.getStore()
64
63
  if (!(store && store.sqlAnalyzed)) {
65
- const iastContext = getIastContext(store)
66
- if (this._isInvalidContext(store, iastContext)) return
67
- this._reportIfVulnerable(value, iastContext, dialect)
68
- }
69
- }
70
-
71
- _reportIfVulnerable (value, context, dialect) {
72
- if (this._isVulnerable(value, context) && this._checkOCE(context)) {
73
- this._report(value, context, dialect)
74
- return true
75
- }
76
- return false
77
- }
78
-
79
- _report (value, context, dialect) {
80
- const evidence = this._getEvidence(value, context, dialect)
81
- const location = this._getLocation()
82
- if (!this._isExcluded(location)) {
83
- const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
84
- const vulnerability = this._createVulnerability(this._type, evidence, spanId, location)
85
- addVulnerability(context, vulnerability)
64
+ super.analyze(value, store, dialect)
86
65
  }
87
66
  }
88
67
 
@@ -22,8 +22,12 @@ class Analyzer extends SinkIastPlugin {
22
22
  return false
23
23
  }
24
24
 
25
- _report (value, context) {
26
- const evidence = this._getEvidence(value, context)
25
+ _report (value, context, meta) {
26
+ const evidence = this._getEvidence(value, context, meta)
27
+ this._reportEvidence(value, context, evidence)
28
+ }
29
+
30
+ _reportEvidence (value, context, evidence) {
27
31
  const location = this._getLocation(value)
28
32
  if (!this._isExcluded(location)) {
29
33
  const locationSourceMap = this._replaceLocationFromSourceMap(location)
@@ -33,9 +37,9 @@ class Analyzer extends SinkIastPlugin {
33
37
  }
34
38
  }
35
39
 
36
- _reportIfVulnerable (value, context) {
40
+ _reportIfVulnerable (value, context, meta) {
37
41
  if (this._isVulnerable(value, context) && this._checkOCE(context, value)) {
38
- this._report(value, context)
42
+ this._report(value, context, meta)
39
43
  return true
40
44
  }
41
45
  return false
@@ -71,11 +75,11 @@ class Analyzer extends SinkIastPlugin {
71
75
  return store && !iastContext
72
76
  }
73
77
 
74
- analyze (value, store = storage.getStore()) {
78
+ analyze (value, store = storage.getStore(), meta) {
75
79
  const iastContext = getIastContext(store)
76
80
  if (this._isInvalidContext(store, iastContext)) return
77
81
 
78
- this._reportIfVulnerable(value, iastContext)
82
+ this._reportIfVulnerable(value, iastContext, meta)
79
83
  }
80
84
 
81
85
  analyzeAll (...values) {
@@ -0,0 +1,90 @@
1
+ 'use strict'
2
+
3
+ const { storage } = require('../../../../../datadog-core')
4
+ const iastContextFunctions = require('../iast-context')
5
+ const overheadController = require('../overhead-controller')
6
+ const { IastPlugin } = require('../iast-plugin')
7
+ const { IAST_ENABLED_TAG_KEY } = require('../tags')
8
+ const { createTransaction, removeTransaction } = require('../taint-tracking/operations')
9
+ const vulnerabilityReporter = require('../vulnerability-reporter')
10
+ const { TagKey } = require('../telemetry/iast-metric')
11
+
12
+ class IastContextPlugin extends IastPlugin {
13
+ startCtxOn (channelName, tag) {
14
+ super.addSub(channelName, (message) => this.startContext())
15
+
16
+ this._getAndRegisterSubscription({
17
+ channelName,
18
+ tag,
19
+ tagKey: TagKey.SOURCE_TYPE
20
+ })
21
+ }
22
+
23
+ finishCtxOn (channelName) {
24
+ super.addSub(channelName, (message) => this.finishContext())
25
+ }
26
+
27
+ getRootSpan (store) {
28
+ return store?.span
29
+ }
30
+
31
+ getTopContext () {
32
+ return {}
33
+ }
34
+
35
+ newIastContext (rootSpan) {
36
+ return { rootSpan }
37
+ }
38
+
39
+ addIastEnabledTag (isRequestAcquired, rootSpan) {
40
+ if (rootSpan?.addTags) {
41
+ rootSpan.addTags({
42
+ [IAST_ENABLED_TAG_KEY]: isRequestAcquired ? 1 : 0
43
+ })
44
+ }
45
+ }
46
+
47
+ startContext () {
48
+ let isRequestAcquired = false
49
+ let iastContext
50
+
51
+ const store = storage.getStore()
52
+ if (store) {
53
+ const topContext = this.getTopContext()
54
+ const rootSpan = this.getRootSpan(store)
55
+
56
+ isRequestAcquired = overheadController.acquireRequest(rootSpan)
57
+ if (isRequestAcquired) {
58
+ iastContext = iastContextFunctions.saveIastContext(store, topContext, this.newIastContext(rootSpan))
59
+ createTransaction(rootSpan.context().toSpanId(), iastContext)
60
+ overheadController.initializeRequestContext(iastContext)
61
+ }
62
+ this.addIastEnabledTag(isRequestAcquired, rootSpan)
63
+ }
64
+
65
+ return {
66
+ isRequestAcquired,
67
+ iastContext,
68
+ store
69
+ }
70
+ }
71
+
72
+ finishContext () {
73
+ const store = storage.getStore()
74
+ if (store) {
75
+ const topContext = this.getTopContext()
76
+ const iastContext = iastContextFunctions.getIastContext(store, topContext)
77
+ const rootSpan = iastContext?.rootSpan
78
+ if (iastContext && rootSpan) {
79
+ vulnerabilityReporter.sendVulnerabilities(iastContext.vulnerabilities, rootSpan)
80
+ removeTransaction(iastContext)
81
+ }
82
+
83
+ if (iastContextFunctions.cleanIastContext(store, topContext, iastContext)) {
84
+ overheadController.releaseRequest()
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ module.exports = IastContextPlugin
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const { KAFKA_MESSAGE_KEY, KAFKA_MESSAGE_VALUE } = require('../taint-tracking/source-types')
4
+ const IastContextPlugin = require('./context-plugin')
5
+
6
+ class KafkaContextPlugin extends IastContextPlugin {
7
+ onConfigure () {
8
+ this.startCtxOn('dd-trace:kafkajs:consumer:afterStart', [KAFKA_MESSAGE_KEY, KAFKA_MESSAGE_VALUE])
9
+
10
+ this.finishCtxOn('dd-trace:kafkajs:consumer:beforeFinish')
11
+ }
12
+ }
13
+
14
+ module.exports = new KafkaContextPlugin()
@@ -101,6 +101,14 @@ class IastPlugin extends Plugin {
101
101
  }
102
102
  }
103
103
 
104
+ enable () {
105
+ this.configure(true)
106
+ }
107
+
108
+ disable () {
109
+ this.configure(false)
110
+ }
111
+
104
112
  onConfigure () {}
105
113
 
106
114
  configure (config) {
@@ -127,10 +135,13 @@ class IastPlugin extends Plugin {
127
135
  if (!channelName && !moduleName) return
128
136
 
129
137
  if (!moduleName) {
130
- const firstSep = channelName.indexOf(':')
138
+ let firstSep = channelName.indexOf(':')
131
139
  if (firstSep === -1) {
132
140
  moduleName = channelName
133
141
  } else {
142
+ if (channelName.startsWith('tracing:')) {
143
+ firstSep = channelName.indexOf(':', 'tracing:'.length + 1)
144
+ }
134
145
  const lastSep = channelName.indexOf(':', firstSep + 1)
135
146
  moduleName = channelName.substring(firstSep + 1, lastSep !== -1 ? lastSep : channelName.length)
136
147
  }
@@ -22,7 +22,7 @@ const requestClose = dc.channel('dd-trace:incomingHttpRequestEnd')
22
22
  const iastResponseEnd = dc.channel('datadog:iast:response-end')
23
23
 
24
24
  function enable (config, _tracer) {
25
- iastTelemetry.configure(config, config.iast && config.iast.telemetryVerbosity)
25
+ iastTelemetry.configure(config, config.iast?.telemetryVerbosity)
26
26
  enableAllAnalyzers(config)
27
27
  enableTaintTracking(config.iast, iastTelemetry.verbosity)
28
28
  requestStart.subscribe(onIncomingHttpRequestStart)
@@ -43,7 +43,7 @@ function disable () {
43
43
  }
44
44
 
45
45
  function onIncomingHttpRequestStart (data) {
46
- if (data && data.req) {
46
+ if (data?.req) {
47
47
  const store = storage.getStore()
48
48
  if (store) {
49
49
  const topContext = web.getContext(data.req)
@@ -68,11 +68,11 @@ function onIncomingHttpRequestStart (data) {
68
68
  }
69
69
 
70
70
  function onIncomingHttpRequestEnd (data) {
71
- if (data && data.req) {
71
+ if (data?.req) {
72
72
  const store = storage.getStore()
73
73
  const topContext = web.getContext(data.req)
74
74
  const iastContext = iastContextFunctions.getIastContext(store, topContext)
75
- if (iastContext && iastContext.rootSpan) {
75
+ if (iastContext?.rootSpan) {
76
76
  iastResponseEnd.publish(data)
77
77
 
78
78
  const vulnerabilities = iastContext.vulnerabilities
@@ -52,7 +52,7 @@ function _resetGlobalContext () {
52
52
  }
53
53
 
54
54
  function acquireRequest (rootSpan) {
55
- if (availableRequest > 0) {
55
+ if (availableRequest > 0 && rootSpan) {
56
56
  const sampling = config && typeof config.requestSampling === 'number'
57
57
  ? config.requestSampling : 30
58
58
  if (rootSpan.context().toSpanId().slice(-2) <= sampling) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const csiMethods = [
4
4
  { src: 'concat' },
5
+ { src: 'parse' },
5
6
  { src: 'plusOperator', operator: true },
6
7
  { src: 'random' },
7
8
  { src: 'replace' },
@@ -10,18 +10,28 @@ const {
10
10
  } = require('./operations')
11
11
 
12
12
  const taintTrackingPlugin = require('./plugin')
13
+ const kafkaConsumerPlugin = require('./plugins/kafka')
14
+
15
+ const kafkaContextPlugin = require('../context/kafka-ctx-plugin')
13
16
 
14
17
  module.exports = {
15
18
  enableTaintTracking (config, telemetryVerbosity) {
16
19
  enableRewriter(telemetryVerbosity)
17
20
  enableTaintOperations(telemetryVerbosity)
18
21
  taintTrackingPlugin.enable()
22
+
23
+ kafkaContextPlugin.enable()
24
+ kafkaConsumerPlugin.enable()
25
+
19
26
  setMaxTransactions(config.maxConcurrentRequests)
20
27
  },
21
28
  disableTaintTracking () {
22
29
  disableRewriter()
23
30
  disableTaintOperations()
24
31
  taintTrackingPlugin.disable()
32
+
33
+ kafkaContextPlugin.disable()
34
+ kafkaConsumerPlugin.disable()
25
35
  },
26
36
  setMaxTransactions: setMaxTransactions,
27
37
  createTransaction: createTransaction,
@@ -0,0 +1,53 @@
1
+ 'use strict'
2
+
3
+ const TaintedUtils = require('@datadog/native-iast-taint-tracking')
4
+ const { IAST_TRANSACTION_ID } = require('../iast-context')
5
+ const iastLog = require('../iast-log')
6
+
7
+ function taintObject (iastContext, object, type, keyTainting, keyType) {
8
+ let result = object
9
+ const transactionId = iastContext?.[IAST_TRANSACTION_ID]
10
+ if (transactionId) {
11
+ const queue = [{ parent: null, property: null, value: object }]
12
+ const visited = new WeakSet()
13
+
14
+ while (queue.length > 0) {
15
+ const { parent, property, value, key } = queue.pop()
16
+ if (value === null) {
17
+ continue
18
+ }
19
+
20
+ try {
21
+ if (typeof value === 'string') {
22
+ const tainted = TaintedUtils.newTaintedString(transactionId, value, property, type)
23
+ if (!parent) {
24
+ result = tainted
25
+ } else if (keyTainting && key) {
26
+ const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
27
+ parent[taintedProperty] = tainted
28
+ } else {
29
+ parent[key] = tainted
30
+ }
31
+ } else if (typeof value === 'object' && !visited.has(value)) {
32
+ visited.add(value)
33
+
34
+ for (const key of Object.keys(value)) {
35
+ queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key], key })
36
+ }
37
+
38
+ if (parent && keyTainting && key) {
39
+ const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
40
+ parent[taintedProperty] = value
41
+ }
42
+ }
43
+ } catch (e) {
44
+ iastLog.error(`Error visiting property : ${property}`).errorAndPublish(e)
45
+ }
46
+ }
47
+ }
48
+ return result
49
+ }
50
+
51
+ module.exports = {
52
+ taintObject
53
+ }
@@ -2,11 +2,11 @@
2
2
 
3
3
  const TaintedUtils = require('@datadog/native-iast-taint-tracking')
4
4
  const { IAST_TRANSACTION_ID } = require('../iast-context')
5
- const iastLog = require('../iast-log')
6
5
  const iastTelemetry = require('../telemetry')
7
6
  const { REQUEST_TAINTED } = require('../telemetry/iast-metric')
8
7
  const { isInfoAllowed } = require('../telemetry/verbosity')
9
8
  const { getTaintTrackingImpl, getTaintTrackingNoop } = require('./taint-tracking-impl')
9
+ const { taintObject } = require('./operations-taint-object')
10
10
 
11
11
  function createTransaction (id, iastContext) {
12
12
  if (id && iastContext) {
@@ -34,7 +34,7 @@ function removeTransaction (iastContext) {
34
34
  }
35
35
 
36
36
  function newTaintedString (iastContext, string, name, type) {
37
- let result = string
37
+ let result
38
38
  const transactionId = iastContext?.[IAST_TRANSACTION_ID]
39
39
  if (transactionId) {
40
40
  result = TaintedUtils.newTaintedString(transactionId, string, name, type)
@@ -44,56 +44,19 @@ function newTaintedString (iastContext, string, name, type) {
44
44
  return result
45
45
  }
46
46
 
47
- function taintObject (iastContext, object, type, keyTainting, keyType) {
48
- let result = object
47
+ function newTaintedObject (iastContext, obj, name, type) {
48
+ let result
49
49
  const transactionId = iastContext?.[IAST_TRANSACTION_ID]
50
50
  if (transactionId) {
51
- const queue = [{ parent: null, property: null, value: object }]
52
- const visited = new WeakSet()
53
-
54
- while (queue.length > 0) {
55
- const { parent, property, value, key } = queue.pop()
56
- if (value === null) {
57
- continue
58
- }
59
-
60
- try {
61
- if (typeof value === 'string') {
62
- const tainted = TaintedUtils.newTaintedString(transactionId, value, property, type)
63
- if (!parent) {
64
- result = tainted
65
- } else {
66
- if (keyTainting && key) {
67
- const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
68
- parent[taintedProperty] = tainted
69
- } else {
70
- parent[key] = tainted
71
- }
72
- }
73
- } else if (typeof value === 'object' && !visited.has(value)) {
74
- visited.add(value)
75
-
76
- const keys = Object.keys(value)
77
- for (let i = 0; i < keys.length; i++) {
78
- const key = keys[i]
79
- queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key], key })
80
- }
81
-
82
- if (parent && keyTainting && key) {
83
- const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
84
- parent[taintedProperty] = value
85
- }
86
- }
87
- } catch (e) {
88
- iastLog.error(`Error visiting property : ${property}`).errorAndPublish(e)
89
- }
90
- }
51
+ result = TaintedUtils.newTaintedObject(transactionId, obj, name, type)
52
+ } else {
53
+ result = obj
91
54
  }
92
55
  return result
93
56
  }
94
57
 
95
58
  function isTainted (iastContext, string) {
96
- let result = false
59
+ let result
97
60
  const transactionId = iastContext?.[IAST_TRANSACTION_ID]
98
61
  if (transactionId) {
99
62
  result = TaintedUtils.isTainted(transactionId, string)
@@ -104,7 +67,7 @@ function isTainted (iastContext, string) {
104
67
  }
105
68
 
106
69
  function getRanges (iastContext, string) {
107
- let result = []
70
+ let result
108
71
  const transactionId = iastContext?.[IAST_TRANSACTION_ID]
109
72
  if (transactionId) {
110
73
  result = TaintedUtils.getRanges(transactionId, string)
@@ -148,6 +111,7 @@ module.exports = {
148
111
  createTransaction,
149
112
  removeTransaction,
150
113
  newTaintedString,
114
+ newTaintedObject,
151
115
  taintObject,
152
116
  isTainted,
153
117
  getRanges,
@@ -3,7 +3,7 @@
3
3
  const { SourceIastPlugin } = require('../iast-plugin')
4
4
  const { getIastContext } = require('../iast-context')
5
5
  const { storage } = require('../../../../../datadog-core')
6
- const { taintObject, newTaintedString } = require('./operations')
6
+ const { taintObject, newTaintedString, getRanges } = require('./operations')
7
7
  const {
8
8
  HTTP_REQUEST_BODY,
9
9
  HTTP_REQUEST_COOKIE_VALUE,
@@ -65,6 +65,18 @@ class TaintTrackingPlugin extends SourceIastPlugin {
65
65
  }
66
66
  )
67
67
 
68
+ this.addSub(
69
+ { channelName: 'apm:graphql:resolve:start', tag: HTTP_REQUEST_BODY },
70
+ (data) => {
71
+ const iastContext = getIastContext(storage.getStore())
72
+ const source = data.context?.source
73
+ const ranges = source && getRanges(iastContext, source)
74
+ if (ranges?.length) {
75
+ this._taintTrackingHandler(ranges[0].iinfo.type, data.args, null, iastContext)
76
+ }
77
+ }
78
+ )
79
+
68
80
  // this is a special case to increment INSTRUMENTED_SOURCE metric for header
69
81
  this.addInstrumentedSource('http', [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME])
70
82
  }
@@ -104,14 +116,6 @@ class TaintTrackingPlugin extends SourceIastPlugin {
104
116
  this.taintHeaders(req.headers, iastContext)
105
117
  this.taintUrl(req, iastContext)
106
118
  }
107
-
108
- enable () {
109
- this.configure(true)
110
- }
111
-
112
- disable () {
113
- this.configure(false)
114
- }
115
119
  }
116
120
 
117
121
  module.exports = new TaintTrackingPlugin()
@@ -0,0 +1,47 @@
1
+ 'use strict'
2
+
3
+ const shimmer = require('../../../../../../datadog-shimmer')
4
+ const { storage } = require('../../../../../../datadog-core')
5
+ const { getIastContext } = require('../../iast-context')
6
+ const { KAFKA_MESSAGE_KEY, KAFKA_MESSAGE_VALUE } = require('../source-types')
7
+ const { newTaintedObject, newTaintedString } = require('../operations')
8
+ const { SourceIastPlugin } = require('../../iast-plugin')
9
+
10
+ class KafkaConsumerIastPlugin extends SourceIastPlugin {
11
+ onConfigure () {
12
+ this.addSub({ channelName: 'dd-trace:kafkajs:consumer:afterStart', tag: [KAFKA_MESSAGE_KEY, KAFKA_MESSAGE_VALUE] },
13
+ ({ message }) => this.taintKafkaMessage(message)
14
+ )
15
+ }
16
+
17
+ getToStringWrap (toString, iastContext, type) {
18
+ return function () {
19
+ const res = toString.apply(this, arguments)
20
+ return newTaintedString(iastContext, res, undefined, type)
21
+ }
22
+ }
23
+
24
+ taintKafkaMessage (message) {
25
+ const iastContext = getIastContext(storage.getStore())
26
+
27
+ if (iastContext && message) {
28
+ const { key, value } = message
29
+
30
+ if (key && typeof key === 'object') {
31
+ shimmer.wrap(key, 'toString',
32
+ toString => this.getToStringWrap(toString, iastContext, KAFKA_MESSAGE_KEY))
33
+
34
+ newTaintedObject(iastContext, key, undefined, KAFKA_MESSAGE_KEY)
35
+ }
36
+
37
+ if (value && typeof value === 'object') {
38
+ shimmer.wrap(value, 'toString',
39
+ toString => this.getToStringWrap(toString, iastContext, KAFKA_MESSAGE_VALUE))
40
+
41
+ newTaintedObject(iastContext, value, undefined, KAFKA_MESSAGE_VALUE)
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ module.exports = new KafkaConsumerIastPlugin()
@@ -9,5 +9,7 @@ module.exports = {
9
9
  HTTP_REQUEST_PARAMETER: 'http.request.parameter',
10
10
  HTTP_REQUEST_PATH: 'http.request.path',
11
11
  HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter',
12
- HTTP_REQUEST_URI: 'http.request.uri'
12
+ HTTP_REQUEST_URI: 'http.request.uri',
13
+ KAFKA_MESSAGE_KEY: 'kafka.message.key',
14
+ KAFKA_MESSAGE_VALUE: 'kafka.message.value'
13
15
  }
@@ -7,15 +7,19 @@ const iastContextFunctions = require('../iast-context')
7
7
  const iastLog = require('../iast-log')
8
8
  const { EXECUTED_PROPAGATION } = require('../telemetry/iast-metric')
9
9
  const { isDebugAllowed } = require('../telemetry/verbosity')
10
+ const { taintObject } = require('./operations-taint-object')
10
11
 
11
12
  const mathRandomCallCh = dc.channel('datadog:random:call')
12
13
 
14
+ const JSON_VALUE = 'json.value'
15
+
13
16
  function noop (res) { return res }
14
17
  // NOTE: methods of this object must be synchronized with csi-methods.js file definitions!
15
18
  // Otherwise you may end up rewriting a method and not providing its rewritten implementation
16
19
  const TaintTrackingNoop = {
17
- plusOperator: noop,
18
20
  concat: noop,
21
+ parse: noop,
22
+ plusOperator: noop,
19
23
  random: noop,
20
24
  replace: noop,
21
25
  slice: noop,
@@ -26,7 +30,7 @@ const TaintTrackingNoop = {
26
30
  }
27
31
 
28
32
  function getTransactionId (iastContext) {
29
- return iastContext && iastContext[iastContextFunctions.IAST_TRANSACTION_ID]
33
+ return iastContext?.[iastContextFunctions.IAST_TRANSACTION_ID]
30
34
  }
31
35
 
32
36
  function getContextDefault () {
@@ -120,6 +124,29 @@ function csiMethodsOverrides (getContext) {
120
124
  if (mathRandomCallCh.hasSubscribers) {
121
125
  mathRandomCallCh.publish({ fn })
122
126
  }
127
+ return res
128
+ },
129
+
130
+ parse: function (res, fn, target, json) {
131
+ if (fn === JSON.parse) {
132
+ try {
133
+ const iastContext = getContext()
134
+ const transactionId = getTransactionId(iastContext)
135
+ if (transactionId) {
136
+ const ranges = TaintedUtils.getRanges(transactionId, json)
137
+
138
+ // TODO: first version.
139
+ // here we are losing the original source because taintObject always creates a new tainted
140
+ if (ranges?.length > 0) {
141
+ const range = ranges.find(range => range.iinfo?.type)
142
+ res = taintObject(iastContext, res, range?.iinfo.type || JSON_VALUE)
143
+ }
144
+ }
145
+ } catch (e) {
146
+ iastLog.error(e)
147
+ }
148
+ }
149
+
123
150
  return res
124
151
  }
125
152
  }
@@ -20,7 +20,7 @@ function iterateObject (target, fn, levelKeys = [], depth = 50) {
20
20
 
21
21
  fn(val, nextLevelKeys, target, key)
22
22
 
23
- if (val !== null && typeof val === 'object') {
23
+ if (val !== null && typeof val === 'object' && depth > 0) {
24
24
  iterateObject(val, fn, nextLevelKeys, depth - 1)
25
25
  }
26
26
  })