dd-trace 5.3.0 → 5.5.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 (44) hide show
  1. package/CONTRIBUTING.md +98 -0
  2. package/README.md +4 -102
  3. package/ci/cypress/after-run.js +1 -0
  4. package/package.json +2 -2
  5. package/packages/datadog-instrumentations/src/cucumber.js +156 -42
  6. package/packages/datadog-instrumentations/src/jest.js +84 -49
  7. package/packages/datadog-instrumentations/src/mocha.js +139 -13
  8. package/packages/datadog-plugin-amqplib/src/consumer.js +5 -2
  9. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +60 -50
  10. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +40 -17
  11. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -26
  12. package/packages/datadog-plugin-cucumber/src/index.js +25 -9
  13. package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
  14. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +560 -0
  15. package/packages/datadog-plugin-cypress/src/plugin.js +6 -533
  16. package/packages/datadog-plugin-jest/src/index.js +4 -8
  17. package/packages/datadog-plugin-kafkajs/src/consumer.js +16 -0
  18. package/packages/datadog-plugin-mocha/src/index.js +38 -17
  19. package/packages/datadog-plugin-rhea/src/consumer.js +4 -1
  20. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  21. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  22. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +8 -0
  23. package/packages/dd-trace/src/appsec/iast/index.js +4 -4
  24. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  25. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  26. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  27. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  28. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  29. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  31. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  32. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +29 -2
  33. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  34. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  35. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  36. package/packages/dd-trace/src/config.js +3 -2
  37. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -1
  38. package/packages/dd-trace/src/opentracing/span.js +4 -4
  39. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
  40. package/packages/dd-trace/src/plugins/util/test.js +17 -1
  41. package/packages/dd-trace/src/profiling/exporters/agent.js +40 -31
  42. package/packages/dd-trace/src/telemetry/index.js +3 -0
  43. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  44. package/packages/dd-trace/src/telemetry/send-data.js +0 -3
@@ -16,7 +16,11 @@ const {
16
16
  TEST_ITR_FORCED_RUN,
17
17
  TEST_CODE_OWNERS,
18
18
  ITR_CORRELATION_ID,
19
- TEST_SOURCE_FILE
19
+ TEST_SOURCE_FILE,
20
+ removeEfdStringFromTestName,
21
+ TEST_IS_NEW,
22
+ TEST_EARLY_FLAKE_IS_RETRY,
23
+ TEST_EARLY_FLAKE_IS_ENABLED
20
24
  } = require('../../dd-trace/src/plugins/util/test')
21
25
  const { COMPONENT } = require('../../dd-trace/src/constants')
22
26
  const {
@@ -39,7 +43,7 @@ class MochaPlugin extends CiPlugin {
39
43
  super(...args)
40
44
 
41
45
  this._testSuites = new Map()
42
- this._testNameToParams = {}
46
+ this._testTitleToParams = {}
43
47
  this.sourceRoot = process.cwd()
44
48
 
45
49
  this.addSub('ci:mocha:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
@@ -131,9 +135,9 @@ class MochaPlugin extends CiPlugin {
131
135
  }
132
136
  })
133
137
 
134
- this.addSub('ci:mocha:test:start', ({ test, testStartLine }) => {
138
+ this.addSub('ci:mocha:test:start', (testInfo) => {
135
139
  const store = storage.getStore()
136
- const span = this.startTestSpan(test, testStartLine)
140
+ const span = this.startTestSpan(testInfo)
137
141
 
138
142
  this.enter(span, store)
139
143
  })
@@ -156,12 +160,12 @@ class MochaPlugin extends CiPlugin {
156
160
  }
157
161
  })
158
162
 
159
- this.addSub('ci:mocha:test:skip', (test) => {
163
+ this.addSub('ci:mocha:test:skip', (testInfo) => {
160
164
  const store = storage.getStore()
161
165
  // skipped through it.skip, so the span is not created yet
162
166
  // for this test
163
167
  if (!store) {
164
- const testSpan = this.startTestSpan(test)
168
+ const testSpan = this.startTestSpan(testInfo)
165
169
  this.enter(testSpan, store)
166
170
  }
167
171
  })
@@ -179,8 +183,8 @@ class MochaPlugin extends CiPlugin {
179
183
  }
180
184
  })
181
185
 
182
- this.addSub('ci:mocha:test:parameterize', ({ name, params }) => {
183
- this._testNameToParams[name] = params
186
+ this.addSub('ci:mocha:test:parameterize', ({ title, params }) => {
187
+ this._testTitleToParams[title] = params
184
188
  })
185
189
 
186
190
  this.addSub('ci:mocha:session:finish', ({
@@ -190,7 +194,8 @@ class MochaPlugin extends CiPlugin {
190
194
  numSkippedSuites,
191
195
  hasForcedToRunSuites,
192
196
  hasUnskippableSuites,
193
- error
197
+ error,
198
+ isEarlyFlakeDetectionEnabled
194
199
  }) => {
195
200
  if (this.testSessionSpan) {
196
201
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
@@ -217,6 +222,10 @@ class MochaPlugin extends CiPlugin {
217
222
  }
218
223
  )
219
224
 
225
+ if (isEarlyFlakeDetectionEnabled) {
226
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
227
+ }
228
+
220
229
  this.testModuleSpan.finish()
221
230
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
222
231
  this.testSessionSpan.finish()
@@ -228,12 +237,19 @@ class MochaPlugin extends CiPlugin {
228
237
  })
229
238
  }
230
239
 
231
- startTestSpan (test, testStartLine) {
232
- const testName = test.fullTitle()
233
- const { file: testSuiteAbsolutePath, title } = test
240
+ startTestSpan (testInfo) {
241
+ const {
242
+ testSuiteAbsolutePath,
243
+ title,
244
+ isNew,
245
+ isEfdRetry,
246
+ testStartLine
247
+ } = testInfo
248
+
249
+ const testName = removeEfdStringFromTestName(testInfo.testName)
234
250
 
235
251
  const extraTags = {}
236
- const testParametersString = getTestParametersString(this._testNameToParams, title)
252
+ const testParametersString = getTestParametersString(this._testTitleToParams, title)
237
253
  if (testParametersString) {
238
254
  extraTags[TEST_PARAMETERS] = testParametersString
239
255
  }
@@ -245,14 +261,19 @@ class MochaPlugin extends CiPlugin {
245
261
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
246
262
  const testSuiteSpan = this._testSuites.get(testSuiteAbsolutePath)
247
263
 
248
- const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
249
-
250
- if (testSourceFile) {
251
- extraTags[TEST_SOURCE_FILE] = testSourceFile
264
+ if (this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot) {
265
+ extraTags[TEST_SOURCE_FILE] = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
252
266
  } else {
253
267
  extraTags[TEST_SOURCE_FILE] = testSuite
254
268
  }
255
269
 
270
+ if (isNew) {
271
+ extraTags[TEST_IS_NEW] = 'true'
272
+ if (isEfdRetry) {
273
+ extraTags[TEST_EARLY_FLAKE_IS_RETRY] = 'true'
274
+ }
275
+ }
276
+
256
277
  return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
257
278
  }
258
279
  }
@@ -31,7 +31,10 @@ class RheaConsumerPlugin extends ConsumerPlugin {
31
31
  }
32
32
  })
33
33
 
34
- if (this.config.dsmEnabled && msgObj.message) {
34
+ if (
35
+ this.config.dsmEnabled &&
36
+ msgObj?.message?.delivery_annotations?.[CONTEXT_PROPAGATION_KEY]
37
+ ) {
35
38
  const payloadSize = getAmqpMessageSize(
36
39
  { headers: msgObj.message.delivery_annotations, content: msgObj.message.body }
37
40
  )
@@ -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) {
@@ -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
  })
@@ -14,5 +14,6 @@ module.exports = {
14
14
  APM_TRACING_SAMPLE_RATE: 1n << 12n,
15
15
  APM_TRACING_LOGS_INJECTION: 1n << 13n,
16
16
  APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n,
17
- APM_TRACING_CUSTOM_TAGS: 1n << 15n
17
+ APM_TRACING_CUSTOM_TAGS: 1n << 15n,
18
+ APM_TRACING_ENABLED: 1n << 19n
18
19
  }