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.
- package/CONTRIBUTING.md +98 -0
- package/README.md +4 -102
- package/ci/cypress/after-run.js +1 -0
- package/package.json +2 -2
- package/packages/datadog-instrumentations/src/cucumber.js +156 -42
- package/packages/datadog-instrumentations/src/jest.js +84 -49
- package/packages/datadog-instrumentations/src/mocha.js +139 -13
- package/packages/datadog-plugin-amqplib/src/consumer.js +5 -2
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +60 -50
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +40 -17
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -26
- package/packages/datadog-plugin-cucumber/src/index.js +25 -9
- package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +560 -0
- package/packages/datadog-plugin-cypress/src/plugin.js +6 -533
- package/packages/datadog-plugin-jest/src/index.js +4 -8
- package/packages/datadog-plugin-kafkajs/src/consumer.js +16 -0
- package/packages/datadog-plugin-mocha/src/index.js +38 -17
- package/packages/datadog-plugin-rhea/src/consumer.js +4 -1
- package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
- package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +8 -0
- package/packages/dd-trace/src/appsec/iast/index.js +4 -4
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +29 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
- package/packages/dd-trace/src/config.js +3 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -1
- package/packages/dd-trace/src/opentracing/span.js +4 -4
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +17 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +40 -31
- package/packages/dd-trace/src/telemetry/index.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- 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.
|
|
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', (
|
|
138
|
+
this.addSub('ci:mocha:test:start', (testInfo) => {
|
|
135
139
|
const store = storage.getStore()
|
|
136
|
-
const span = this.startTestSpan(
|
|
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', (
|
|
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(
|
|
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', ({
|
|
183
|
-
this.
|
|
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 (
|
|
232
|
-
const
|
|
233
|
-
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
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()
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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) {
|
|
@@ -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
|
|
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
|
|
48
|
-
let result
|
|
47
|
+
function newTaintedObject (iastContext, obj, name, type) {
|
|
48
|
+
let result
|
|
49
49
|
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
50
50
|
if (transactionId) {
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
})
|