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.
- package/LICENSE-3rdparty.csv +1 -0
- package/README.md +1 -32
- package/ci/init.js +1 -4
- package/index.d.ts +21 -0
- package/package.json +7 -6
- package/packages/datadog-instrumentations/src/amqplib.js +1 -1
- package/packages/datadog-instrumentations/src/child_process.js +150 -0
- package/packages/datadog-instrumentations/src/cucumber.js +12 -12
- package/packages/datadog-instrumentations/src/express.js +20 -0
- package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +149 -11
- package/packages/datadog-instrumentations/src/mocha.js +142 -16
- package/packages/datadog-instrumentations/src/mongoose.js +23 -10
- package/packages/datadog-instrumentations/src/next.js +17 -3
- package/packages/datadog-instrumentations/src/playwright.js +41 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +107 -1
- package/packages/datadog-plugin-child_process/src/index.js +91 -0
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
- package/packages/datadog-plugin-cucumber/src/index.js +16 -11
- package/packages/datadog-plugin-cypress/src/plugin.js +52 -23
- package/packages/datadog-plugin-grpc/src/client.js +16 -2
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +43 -6
- package/packages/datadog-plugin-kafkajs/src/consumer.js +16 -0
- package/packages/datadog-plugin-mocha/src/index.js +47 -17
- package/packages/datadog-plugin-playwright/src/index.js +19 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
- package/packages/datadog-plugin-rhea/src/producer.js +11 -0
- package/packages/dd-trace/src/appsec/addresses.js +2 -0
- package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
- package/packages/dd-trace/src/appsec/channels.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
- 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 +12 -1
- 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/index.js +17 -2
- 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/appsec/rule_manager.js +2 -2
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
- package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
- package/packages/dd-trace/src/config.js +22 -9
- package/packages/dd-trace/src/datastreams/processor.js +6 -0
- package/packages/dd-trace/src/datastreams/writer.js +2 -5
- package/packages/dd-trace/src/dogstatsd.js +3 -5
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
- package/packages/dd-trace/src/exporters/common/request.js +21 -3
- package/packages/dd-trace/src/format.js +25 -1
- package/packages/dd-trace/src/noop/span.js +1 -0
- package/packages/dd-trace/src/opentelemetry/span.js +9 -2
- package/packages/dd-trace/src/opentracing/span.js +38 -0
- package/packages/dd-trace/src/opentracing/span_context.js +12 -6
- package/packages/dd-trace/src/opentracing/tracer.js +2 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +25 -9
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +6 -0
- package/packages/dd-trace/src/plugins/util/test.js +53 -8
- package/packages/dd-trace/src/profiling/config.js +22 -22
- package/packages/dd-trace/src/proxy.js +31 -23
- package/packages/dd-trace/src/span_processor.js +5 -1
- package/packages/dd-trace/src/telemetry/index.js +6 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/telemetry/send-data.js +0 -3
- package/packages/datadog-instrumentations/src/child-process.js +0 -29
- 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,
|
|
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,
|
|
61
|
+
analyze (value, store, dialect) {
|
|
62
|
+
store = store || storage.getStore()
|
|
64
63
|
if (!(store && store.sqlAnalyzed)) {
|
|
65
|
-
|
|
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
|
-
|
|
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
|
|
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
|
})
|