dd-trace 3.9.3 → 3.11.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 +2 -0
- package/README.md +108 -43
- package/ci/init.js +6 -1
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/index.d.ts +23 -2
- package/package.json +8 -6
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/http/server.js +11 -2
- package/packages/datadog-instrumentations/src/jest.js +24 -10
- package/packages/datadog-instrumentations/src/ldapjs.js +91 -0
- package/packages/datadog-instrumentations/src/mocha.js +7 -4
- package/packages/datadog-instrumentations/src/opensearch.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +1 -2
- package/packages/datadog-instrumentations/src/router.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-http/src/server.js +9 -4
- package/packages/datadog-plugin-jest/src/index.js +21 -25
- package/packages/datadog-plugin-mocha/src/index.js +11 -18
- package/packages/datadog-plugin-mongodb-core/src/index.js +3 -3
- package/packages/datadog-plugin-router/src/index.js +6 -3
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/blocking.js +44 -0
- package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +5 -1
- package/packages/dd-trace/src/appsec/gateway/engine/engine.js +1 -1
- package/packages/dd-trace/src/appsec/gateway/engine/index.js +6 -1
- package/packages/dd-trace/src/appsec/gateway/engine/runner.js +0 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +11 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +40 -2
- package/packages/dd-trace/src/appsec/iast/iast-context.js +3 -1
- package/packages/dd-trace/src/appsec/iast/index.js +7 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +6 -5
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +12 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -29
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +16 -15
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +85 -0
- package/packages/dd-trace/src/appsec/index.js +71 -25
- package/packages/dd-trace/src/{plugins/util → appsec}/ip_blocklist.js +0 -0
- package/packages/dd-trace/src/appsec/ip_extractor.js +98 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +25 -3
- package/packages/dd-trace/src/appsec/remote_config/manager.js +1 -1
- package/packages/dd-trace/src/appsec/rule_manager.js +58 -1
- package/packages/dd-trace/src/appsec/templates/blocked.html +99 -0
- package/packages/dd-trace/src/appsec/templates/blocked.json +8 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +66 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +8 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +19 -51
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +202 -0
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +20 -7
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +22 -19
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -18
- package/packages/dd-trace/src/config.js +34 -10
- package/packages/dd-trace/src/exporter.js +3 -0
- package/packages/dd-trace/src/exporters/agent/index.js +4 -0
- package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +82 -0
- package/packages/dd-trace/src/plugin_manager.js +0 -18
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -50
- package/packages/dd-trace/src/plugins/util/ci.js +35 -2
- package/packages/dd-trace/src/plugins/util/test.js +4 -0
- package/packages/dd-trace/src/plugins/util/web.js +2 -106
- package/packages/dd-trace/src/profiling/exporters/agent.js +4 -0
- package/packages/dd-trace/src/proxy.js +3 -17
- package/packages/dd-trace/src/ritm.js +18 -13
- package/packages/dd-trace/src/telemetry/dependencies.js +11 -1
|
@@ -40,9 +40,8 @@ function wrapQuery (query) {
|
|
|
40
40
|
const callbackResource = new AsyncResource('bound-anonymous-fn')
|
|
41
41
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
42
42
|
const processId = this.processID
|
|
43
|
-
|
|
44
43
|
return asyncResource.runInAsyncScope(() => {
|
|
45
|
-
startCh.publish({ params: this.connectionParameters, query: pgQuery, processId })
|
|
44
|
+
startCh.publish({ params: this.connectionParameters, originalQuery: pgQuery.text, query: pgQuery, processId })
|
|
46
45
|
|
|
47
46
|
const finish = asyncResource.bind(function (error) {
|
|
48
47
|
if (error) {
|
|
@@ -21,7 +21,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
21
21
|
super(...args)
|
|
22
22
|
|
|
23
23
|
this.addSub('ci:cucumber:session:finish', () => {
|
|
24
|
-
this.tracer._exporter.
|
|
24
|
+
this.tracer._exporter.flush()
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
this.addSub('ci:cucumber:run:start', ({ testName, fullTestSuite }) => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Plugin = require('../../dd-trace/src/plugins/plugin')
|
|
4
4
|
const { storage } = require('../../datadog-core')
|
|
5
5
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
6
|
-
const { incomingHttpRequestStart } = require('../../dd-trace/src/appsec/gateway/channels')
|
|
6
|
+
const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/gateway/channels')
|
|
7
7
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
8
8
|
|
|
9
9
|
class HttpServerPlugin extends Plugin {
|
|
@@ -16,7 +16,7 @@ class HttpServerPlugin extends Plugin {
|
|
|
16
16
|
|
|
17
17
|
this._parentStore = undefined
|
|
18
18
|
|
|
19
|
-
this.addSub('apm:http:server:request:start', ({ req, res }) => {
|
|
19
|
+
this.addSub('apm:http:server:request:start', ({ req, res, abortController }) => {
|
|
20
20
|
const store = storage.getStore()
|
|
21
21
|
const span = web.startSpan(this.tracer, this.config, req, res, 'web.request')
|
|
22
22
|
|
|
@@ -33,7 +33,7 @@ class HttpServerPlugin extends Plugin {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
if (incomingHttpRequestStart.hasSubscribers) {
|
|
36
|
-
incomingHttpRequestStart.publish({ req, res })
|
|
36
|
+
incomingHttpRequestStart.publish({ req, res, abortController })
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
39
|
|
|
@@ -42,7 +42,8 @@ class HttpServerPlugin extends Plugin {
|
|
|
42
42
|
})
|
|
43
43
|
|
|
44
44
|
this.addSub('apm:http:server:request:exit', ({ req }) => {
|
|
45
|
-
this.
|
|
45
|
+
const span = this._parentStore && this._parentStore.span
|
|
46
|
+
this.enter(span, this._parentStore)
|
|
46
47
|
this._parentStore = undefined
|
|
47
48
|
})
|
|
48
49
|
|
|
@@ -51,6 +52,10 @@ class HttpServerPlugin extends Plugin {
|
|
|
51
52
|
|
|
52
53
|
if (!context || !context.res) return // Not created by a http.Server instance.
|
|
53
54
|
|
|
55
|
+
if (incomingHttpRequestEnd.hasSubscribers) {
|
|
56
|
+
incomingHttpRequestEnd.publish({ req, res: context.res })
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
web.finishAll(context)
|
|
55
60
|
})
|
|
56
61
|
}
|
|
@@ -15,6 +15,8 @@ const {
|
|
|
15
15
|
TEST_SUITE_ID,
|
|
16
16
|
TEST_COMMAND,
|
|
17
17
|
TEST_ITR_TESTS_SKIPPED,
|
|
18
|
+
TEST_SESSION_ITR_CODE_COVERAGE_ENABLED,
|
|
19
|
+
TEST_SESSION_ITR_SKIPPING_ENABLED,
|
|
18
20
|
TEST_CODE_COVERAGE_LINES_TOTAL
|
|
19
21
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
20
22
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -33,7 +35,7 @@ class JestPlugin extends CiPlugin {
|
|
|
33
35
|
// Used to handle the end of a jest worker to be able to flush
|
|
34
36
|
const handler = ([message]) => {
|
|
35
37
|
if (message === CHILD_MESSAGE_END) {
|
|
36
|
-
this.tracer._exporter.
|
|
38
|
+
this.tracer._exporter.flush(() => {
|
|
37
39
|
// eslint-disable-next-line
|
|
38
40
|
// https://github.com/facebook/jest/blob/24ed3b5ecb419c023ee6fdbc838f07cc028fc007/packages/jest-worker/src/workers/processChild.ts#L118-L133
|
|
39
41
|
// Only after the flush is done we clean up open handles
|
|
@@ -48,9 +50,6 @@ class JestPlugin extends CiPlugin {
|
|
|
48
50
|
this.codeOwnersEntries = getCodeOwnersFileEntries()
|
|
49
51
|
|
|
50
52
|
this.addSub('ci:jest:session:start', (command) => {
|
|
51
|
-
if (!this.config.isAgentlessEnabled) {
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
53
|
const store = storage.getStore()
|
|
55
54
|
const childOf = getTestParentSpan(this.tracer)
|
|
56
55
|
const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
|
|
@@ -66,30 +65,32 @@ class JestPlugin extends CiPlugin {
|
|
|
66
65
|
this.enter(testSessionSpan, store)
|
|
67
66
|
})
|
|
68
67
|
|
|
69
|
-
this.addSub('ci:jest:session:finish', ({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
this.addSub('ci:jest:session:finish', ({
|
|
69
|
+
status,
|
|
70
|
+
isSuitesSkipped,
|
|
71
|
+
isSuitesSkippingEnabled,
|
|
72
|
+
isCodeCoverageEnabled,
|
|
73
|
+
testCodeCoverageLinesTotal
|
|
74
|
+
}) => {
|
|
73
75
|
const testSessionSpan = storage.getStore().span
|
|
76
|
+
|
|
74
77
|
testSessionSpan.setTag(TEST_STATUS, status)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
|
|
79
|
+
testSessionSpan.setTag(TEST_SESSION_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
|
|
80
|
+
testSessionSpan.setTag(TEST_SESSION_ITR_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
|
|
81
|
+
|
|
78
82
|
if (testCodeCoverageLinesTotal !== undefined) {
|
|
79
83
|
testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_TOTAL, testCodeCoverageLinesTotal)
|
|
80
84
|
}
|
|
81
85
|
testSessionSpan.finish()
|
|
82
86
|
finishAllTraceSpans(testSessionSpan)
|
|
83
|
-
this.tracer._exporter.
|
|
87
|
+
this.tracer._exporter.flush()
|
|
84
88
|
})
|
|
85
89
|
|
|
86
90
|
// Test suites can be run in a different process from jest's main one.
|
|
87
91
|
// This subscriber changes the configuration objects from jest to inject the trace id
|
|
88
92
|
// of the test session to the processes that run the test suites.
|
|
89
93
|
this.addSub('ci:jest:session:configuration', configs => {
|
|
90
|
-
if (!this.config.isAgentlessEnabled) {
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
94
|
const testSessionSpan = storage.getStore().span
|
|
94
95
|
configs.forEach(config => {
|
|
95
96
|
config._ddTestSessionId = testSessionSpan.context()._traceId.toString(10)
|
|
@@ -98,10 +99,6 @@ class JestPlugin extends CiPlugin {
|
|
|
98
99
|
})
|
|
99
100
|
|
|
100
101
|
this.addSub('ci:jest:test-suite:start', ({ testSuite, testEnvironmentOptions }) => {
|
|
101
|
-
if (!this.config.isAgentlessEnabled) {
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
102
|
const { _ddTestSessionId: testSessionId, _ddTestCommand: testCommand } = testEnvironmentOptions
|
|
106
103
|
|
|
107
104
|
const store = storage.getStore()
|
|
@@ -125,9 +122,6 @@ class JestPlugin extends CiPlugin {
|
|
|
125
122
|
})
|
|
126
123
|
|
|
127
124
|
this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage }) => {
|
|
128
|
-
if (!this.config.isAgentlessEnabled) {
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
125
|
const testSuiteSpan = storage.getStore().span
|
|
132
126
|
testSuiteSpan.setTag(TEST_STATUS, status)
|
|
133
127
|
if (errorMessage) {
|
|
@@ -139,10 +133,12 @@ class JestPlugin extends CiPlugin {
|
|
|
139
133
|
finishAllTraceSpans(testSuiteSpan)
|
|
140
134
|
})
|
|
141
135
|
|
|
136
|
+
/**
|
|
137
|
+
* This can't use `this.itrConfig` like `ci:mocha:test-suite:code-coverage`
|
|
138
|
+
* because this subscription happens in a different process from the one
|
|
139
|
+
* fetching the ITR config.
|
|
140
|
+
*/
|
|
142
141
|
this.addSub('ci:jest:test-suite:code-coverage', (coverageFiles) => {
|
|
143
|
-
if (!this.config.isAgentlessEnabled || !this.config.isIntelligentTestRunnerEnabled) {
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
142
|
const testSuiteSpan = storage.getStore().span
|
|
147
143
|
this.tracer._exporter.exportCoverage({ span: testSuiteSpan, coverageFiles })
|
|
148
144
|
})
|
|
@@ -14,7 +14,10 @@ const {
|
|
|
14
14
|
getTestSuiteCommonTags,
|
|
15
15
|
TEST_SUITE_ID,
|
|
16
16
|
TEST_SESSION_ID,
|
|
17
|
-
TEST_COMMAND
|
|
17
|
+
TEST_COMMAND,
|
|
18
|
+
TEST_ITR_TESTS_SKIPPED,
|
|
19
|
+
TEST_SESSION_ITR_CODE_COVERAGE_ENABLED,
|
|
20
|
+
TEST_SESSION_ITR_SKIPPING_ENABLED
|
|
18
21
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
19
22
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
20
23
|
|
|
@@ -31,9 +34,6 @@ class MochaPlugin extends CiPlugin {
|
|
|
31
34
|
this.sourceRoot = process.cwd()
|
|
32
35
|
|
|
33
36
|
this.addSub('ci:mocha:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
|
|
34
|
-
if (!this.config.isAgentlessEnabled || !this.config.isIntelligentTestRunnerEnabled) {
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
37
|
if (!this.itrConfig || !this.itrConfig.isCodeCoverageEnabled) {
|
|
38
38
|
return
|
|
39
39
|
}
|
|
@@ -49,9 +49,6 @@ class MochaPlugin extends CiPlugin {
|
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
this.addSub('ci:mocha:session:start', (command) => {
|
|
52
|
-
if (!this.config.isAgentlessEnabled) {
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
52
|
const childOf = getTestParentSpan(this.tracer)
|
|
56
53
|
const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
|
|
57
54
|
|
|
@@ -67,9 +64,6 @@ class MochaPlugin extends CiPlugin {
|
|
|
67
64
|
})
|
|
68
65
|
|
|
69
66
|
this.addSub('ci:mocha:test-suite:start', (suite) => {
|
|
70
|
-
if (!this.config.isAgentlessEnabled) {
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
67
|
const store = storage.getStore()
|
|
74
68
|
const testSuiteMetadata = getTestSuiteCommonTags(
|
|
75
69
|
this.command,
|
|
@@ -89,9 +83,6 @@ class MochaPlugin extends CiPlugin {
|
|
|
89
83
|
})
|
|
90
84
|
|
|
91
85
|
this.addSub('ci:mocha:test-suite:finish', (status) => {
|
|
92
|
-
if (!this.config.isAgentlessEnabled) {
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
86
|
const span = storage.getStore().span
|
|
96
87
|
// the test status of the suite may have been set in ci:mocha:test-suite:error already
|
|
97
88
|
if (!span.context()._tags[TEST_STATUS]) {
|
|
@@ -101,9 +92,6 @@ class MochaPlugin extends CiPlugin {
|
|
|
101
92
|
})
|
|
102
93
|
|
|
103
94
|
this.addSub('ci:mocha:test-suite:error', (err) => {
|
|
104
|
-
if (!this.config.isAgentlessEnabled) {
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
95
|
const span = storage.getStore().span
|
|
108
96
|
span.setTag('error', err)
|
|
109
97
|
span.setTag(TEST_STATUS, 'fail')
|
|
@@ -151,14 +139,19 @@ class MochaPlugin extends CiPlugin {
|
|
|
151
139
|
this._testNameToParams[name] = params
|
|
152
140
|
})
|
|
153
141
|
|
|
154
|
-
this.addSub('ci:mocha:session:finish', (status) => {
|
|
142
|
+
this.addSub('ci:mocha:session:finish', ({ status, isSuitesSkipped }) => {
|
|
155
143
|
if (this.testSessionSpan) {
|
|
144
|
+
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
|
|
156
145
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
146
|
+
this.testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
|
|
147
|
+
this.testSessionSpan.setTag(TEST_SESSION_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
|
|
148
|
+
this.testSessionSpan.setTag(TEST_SESSION_ITR_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
|
|
149
|
+
|
|
157
150
|
this.testSessionSpan.finish()
|
|
158
151
|
finishAllTraceSpans(this.testSessionSpan)
|
|
159
152
|
}
|
|
160
|
-
this.tracer._exporter._writer.flush()
|
|
161
153
|
this.itrConfig = null
|
|
154
|
+
this.tracer._exporter.flush()
|
|
162
155
|
})
|
|
163
156
|
}
|
|
164
157
|
|
|
@@ -8,7 +8,7 @@ class MongodbCorePlugin extends DatabasePlugin {
|
|
|
8
8
|
|
|
9
9
|
start ({ ns, ops, options = {}, name }) {
|
|
10
10
|
const query = getQuery(ops)
|
|
11
|
-
const resource = truncate(getResource(ns, query, name))
|
|
11
|
+
const resource = truncate(getResource(this, ns, query, name))
|
|
12
12
|
|
|
13
13
|
this.startSpan('mongodb.query', {
|
|
14
14
|
service: this.config.service,
|
|
@@ -31,10 +31,10 @@ function getQuery (cmd) {
|
|
|
31
31
|
if (cmd.filter) return JSON.stringify(limitDepth(cmd.filter))
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function getResource (ns, query, operationName) {
|
|
34
|
+
function getResource (plugin, ns, query, operationName) {
|
|
35
35
|
const parts = [operationName, ns]
|
|
36
36
|
|
|
37
|
-
if (query) {
|
|
37
|
+
if (plugin.config.queryInResourceName && query) {
|
|
38
38
|
parts.push(query)
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -29,8 +29,9 @@ class RouterPlugin extends WebPlugin {
|
|
|
29
29
|
context.middleware.push(span)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
this.
|
|
32
|
+
const store = storage.getStore()
|
|
33
|
+
this._storeStack.push(store)
|
|
34
|
+
this.enter(span, store)
|
|
34
35
|
|
|
35
36
|
web.patch(req)
|
|
36
37
|
web.setRoute(req, context.route)
|
|
@@ -53,7 +54,9 @@ class RouterPlugin extends WebPlugin {
|
|
|
53
54
|
})
|
|
54
55
|
|
|
55
56
|
this.addSub(`apm:${this.constructor.name}:middleware:exit`, ({ req }) => {
|
|
56
|
-
this.
|
|
57
|
+
const savedStore = this._storeStack.pop()
|
|
58
|
+
const span = savedStore && savedStore.span
|
|
59
|
+
this.enter(span, savedStore)
|
|
57
60
|
})
|
|
58
61
|
|
|
59
62
|
this.addSub(`apm:${this.constructor.name}:middleware:error`, ({ req, error }) => {
|
|
@@ -14,5 +14,7 @@ module.exports = {
|
|
|
14
14
|
HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
|
|
15
15
|
// TODO: 'server.response.trailers',
|
|
16
16
|
HTTP_INCOMING_REMOTE_IP: 'server.request.client_ip',
|
|
17
|
-
HTTP_INCOMING_REMOTE_PORT: 'server.request.client_port'
|
|
17
|
+
HTTP_INCOMING_REMOTE_PORT: 'server.request.client_port',
|
|
18
|
+
|
|
19
|
+
HTTP_CLIENT_IP: 'http.client_ip'
|
|
18
20
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
let templateHtml, templateJson
|
|
5
|
+
function block (req, res, topSpan, abortController) {
|
|
6
|
+
let type
|
|
7
|
+
let body
|
|
8
|
+
|
|
9
|
+
// parse the Accept header, ex: Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
|
|
10
|
+
const accept = req.headers.accept && req.headers.accept.split(',').map((str) => str.split(';', 1)[0].trim())
|
|
11
|
+
|
|
12
|
+
if (accept && accept.includes('text/html') && !accept.includes('application/json')) {
|
|
13
|
+
type = 'text/html'
|
|
14
|
+
body = templateHtml
|
|
15
|
+
} else {
|
|
16
|
+
type = 'application/json'
|
|
17
|
+
body = templateJson
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
topSpan.addTags({
|
|
21
|
+
'appsec.blocked': 'true'
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
res.statusCode = 403
|
|
25
|
+
res.setHeader('Content-Type', type)
|
|
26
|
+
res.setHeader('Content-Length', Buffer.byteLength(body))
|
|
27
|
+
res.end(body)
|
|
28
|
+
|
|
29
|
+
abortController.abort()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function loadTemplates (config) {
|
|
33
|
+
templateHtml = fs.readFileSync(config.appsec.blockedTemplateHtml)
|
|
34
|
+
templateJson = fs.readFileSync(config.appsec.blockedTemplateJson)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function loadTemplatesAsync (config) {
|
|
38
|
+
templateHtml = await fs.promises.readFile(config.appsec.blockedTemplateHtml)
|
|
39
|
+
templateJson = await fs.promises.readFile(config.appsec.blockedTemplateJson)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
block, loadTemplates, loadTemplatesAsync
|
|
44
|
+
}
|
|
@@ -61,7 +61,7 @@ class WAFCallback {
|
|
|
61
61
|
|
|
62
62
|
subscribedAddresses.add(address)
|
|
63
63
|
|
|
64
|
-
Gateway.manager.addSubscription({ addresses: [
|
|
64
|
+
Gateway.manager.addSubscription({ addresses: [address], callback })
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -123,6 +123,10 @@ class WAFCallback {
|
|
|
123
123
|
return result.actions
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
updateRuleData (ruleData) {
|
|
127
|
+
this.ddwaf.updateRuleData(ruleData)
|
|
128
|
+
}
|
|
129
|
+
|
|
126
130
|
clear () {
|
|
127
131
|
this.ddwaf.dispose()
|
|
128
132
|
|
|
@@ -28,7 +28,7 @@ class SubscriptionManager {
|
|
|
28
28
|
const list = this.addressToSubscriptions.get(address)
|
|
29
29
|
|
|
30
30
|
if (list === undefined) {
|
|
31
|
-
this.addressToSubscriptions.set(address, [
|
|
31
|
+
this.addressToSubscriptions.set(address, [subscription])
|
|
32
32
|
} else {
|
|
33
33
|
list.push(subscription)
|
|
34
34
|
}
|
|
@@ -22,6 +22,10 @@ function getContext () {
|
|
|
22
22
|
return store && store.get('context')
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function needsAddress (address) {
|
|
26
|
+
return manager.addresses.has(address)
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
function propagate (data, context = getContext()) {
|
|
26
30
|
if (!context) return
|
|
27
31
|
|
|
@@ -30,7 +34,7 @@ function propagate (data, context = getContext()) {
|
|
|
30
34
|
for (let i = 0; i < keys.length; ++i) {
|
|
31
35
|
const key = keys[i]
|
|
32
36
|
|
|
33
|
-
if (
|
|
37
|
+
if (needsAddress(key)) {
|
|
34
38
|
context.setValue(key, data[key])
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -42,5 +46,6 @@ module.exports = {
|
|
|
42
46
|
manager,
|
|
43
47
|
startContext,
|
|
44
48
|
getContext,
|
|
49
|
+
needsAddress,
|
|
45
50
|
propagate
|
|
46
51
|
}
|
|
@@ -2,5 +2,6 @@ module.exports = {
|
|
|
2
2
|
'WEAK_CIPHER_ANALYZER': require('./weak-cipher-analyzer'),
|
|
3
3
|
'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer'),
|
|
4
4
|
'SQL_INJECTION_ANALYZER': require('./sql-injection-analyzer'),
|
|
5
|
-
'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer')
|
|
5
|
+
'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
|
|
6
|
+
'LDAP_ANALYZER': require('./ldap-injection-analyzer')
|
|
6
7
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
3
|
+
|
|
4
|
+
class LdapInjectionAnalyzer extends InjectionAnalyzer {
|
|
5
|
+
constructor () {
|
|
6
|
+
super('LDAP_INJECTION')
|
|
7
|
+
this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = new LdapInjectionAnalyzer()
|
|
@@ -6,7 +6,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
6
6
|
super('SQL_INJECTION')
|
|
7
7
|
this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql))
|
|
8
8
|
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql))
|
|
9
|
-
this.addSub('apm:pg:query:start', ({
|
|
9
|
+
this.addSub('apm:pg:query:start', ({ originalQuery }) => this.analyze(originalQuery))
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Plugin = require('../../../../src/plugins/plugin')
|
|
4
4
|
const { storage } = require('../../../../../datadog-core')
|
|
5
|
+
const log = require('../../../log')
|
|
5
6
|
const { getFirstNonDDPathAndLine } = require('../path-line')
|
|
6
7
|
const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
|
|
7
8
|
const { getIastContext } = require('../iast-context')
|
|
@@ -13,6 +14,20 @@ class Analyzer extends Plugin {
|
|
|
13
14
|
this._type = type
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
_wrapHandler (handler) {
|
|
18
|
+
return (message, name) => {
|
|
19
|
+
try {
|
|
20
|
+
handler(message, name)
|
|
21
|
+
} catch (e) {
|
|
22
|
+
log.debug(e)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
addSub (channelName, handler) {
|
|
28
|
+
super.addSub(channelName, this._wrapHandler(handler))
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
_isVulnerable (value, context) {
|
|
17
32
|
return false
|
|
18
33
|
}
|
|
@@ -25,6 +40,14 @@ class Analyzer extends Plugin {
|
|
|
25
40
|
addVulnerability(context, vulnerability)
|
|
26
41
|
}
|
|
27
42
|
|
|
43
|
+
_reportIfVulnerable (value, context) {
|
|
44
|
+
if (this._isVulnerable(value, context) && this._checkOCE(context)) {
|
|
45
|
+
this._report(value, context)
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
28
51
|
_getEvidence (value) {
|
|
29
52
|
return { value }
|
|
30
53
|
}
|
|
@@ -35,8 +58,23 @@ class Analyzer extends Plugin {
|
|
|
35
58
|
|
|
36
59
|
analyze (value) {
|
|
37
60
|
const iastContext = getIastContext(storage.getStore())
|
|
38
|
-
if (iastContext
|
|
39
|
-
|
|
61
|
+
if (!iastContext) return
|
|
62
|
+
|
|
63
|
+
this._reportIfVulnerable(value, iastContext)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
analyzeAll (...values) {
|
|
67
|
+
const iastContext = getIastContext(storage.getStore())
|
|
68
|
+
if (!iastContext) return
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < values.length; i++) {
|
|
71
|
+
const value = values[i]
|
|
72
|
+
if (this._isVulnerable(value, iastContext)) {
|
|
73
|
+
if (this._checkOCE(iastContext)) {
|
|
74
|
+
this._report(value, iastContext)
|
|
75
|
+
}
|
|
76
|
+
break
|
|
77
|
+
}
|
|
40
78
|
}
|
|
41
79
|
}
|
|
42
80
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const IAST_CONTEXT_KEY = Symbol('_dd.iast.context')
|
|
2
|
+
const IAST_TRANSACTION_ID = Symbol('_dd.iast.transactionId')
|
|
2
3
|
|
|
3
4
|
function getIastContext (store) {
|
|
4
5
|
return store && store[IAST_CONTEXT_KEY]
|
|
@@ -46,5 +47,6 @@ module.exports = {
|
|
|
46
47
|
getIastContext,
|
|
47
48
|
saveIastContext,
|
|
48
49
|
cleanIastContext,
|
|
49
|
-
IAST_CONTEXT_KEY
|
|
50
|
+
IAST_CONTEXT_KEY,
|
|
51
|
+
IAST_TRANSACTION_ID
|
|
50
52
|
}
|
|
@@ -7,6 +7,8 @@ const dc = require('diagnostics_channel')
|
|
|
7
7
|
const iastContextFunctions = require('./iast-context')
|
|
8
8
|
const { enableTaintTracking, disableTaintTracking, createTransaction, removeTransaction } = require('./taint-tracking')
|
|
9
9
|
|
|
10
|
+
const IAST_ENABLED_TAG_KEY = '_dd.iast.enabled'
|
|
11
|
+
|
|
10
12
|
// TODO Change to `apm:http:server:request:[start|close]` when the subscription
|
|
11
13
|
// order of the callbacks can be enforce
|
|
12
14
|
const requestStart = dc.channel('dd-trace:incomingHttpRequestStart')
|
|
@@ -40,6 +42,11 @@ function onIncomingHttpRequestStart (data) {
|
|
|
40
42
|
createTransaction(rootSpan.context().toSpanId(), iastContext)
|
|
41
43
|
overheadController.initializeRequestContext(iastContext)
|
|
42
44
|
}
|
|
45
|
+
if (rootSpan.addTags) {
|
|
46
|
+
rootSpan.addTags({
|
|
47
|
+
[IAST_ENABLED_TAG_KEY]: isRequestAcquired ? '1' : '0'
|
|
48
|
+
})
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const process = require('process')
|
|
2
3
|
const pathLine = {
|
|
3
4
|
getFirstNonDDPathAndLine,
|
|
4
5
|
getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
|
|
@@ -7,7 +8,7 @@ const pathLine = {
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
const EXCLUDED_PATHS = [
|
|
10
|
-
'
|
|
11
|
+
path.join(path.sep, 'node_modules', 'diagnostics_channel')
|
|
11
12
|
]
|
|
12
13
|
const EXCLUDED_PATH_PREFIXES = [
|
|
13
14
|
'node:diagnostics_channel',
|
|
@@ -20,7 +21,7 @@ const EXCLUDED_PATH_PREFIXES = [
|
|
|
20
21
|
|
|
21
22
|
function calculateDDBasePath (dirname) {
|
|
22
23
|
const dirSteps = dirname.split(path.sep)
|
|
23
|
-
const packagesIndex = dirSteps.
|
|
24
|
+
const packagesIndex = dirSteps.lastIndexOf('packages')
|
|
24
25
|
return dirSteps.slice(0, packagesIndex).join(path.sep) + path.sep
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -43,10 +44,10 @@ function getFirstNonDDPathAndLineFromCallsites (callsites) {
|
|
|
43
44
|
if (callsites) {
|
|
44
45
|
for (let i = 0; i < callsites.length; i++) {
|
|
45
46
|
const callsite = callsites[i]
|
|
46
|
-
const
|
|
47
|
-
if (!isExcluded(callsite) &&
|
|
47
|
+
const filepath = callsite.getFileName()
|
|
48
|
+
if (!isExcluded(callsite) && filepath.indexOf(pathLine.ddBasePath) === -1) {
|
|
48
49
|
return {
|
|
49
|
-
path,
|
|
50
|
+
path: path.relative(process.cwd(), filepath),
|
|
50
51
|
line: callsite.getLineNumber()
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -1,33 +1,6 @@
|
|
|
1
|
-
const { storage } = require('../../../../../datadog-core')
|
|
2
|
-
const iastContextFunctions = require('../iast-context')
|
|
3
|
-
const log = require('../../../log')
|
|
4
1
|
const TaintedUtils = require('@datadog/native-iast-taint-tracking')
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
function noop (res) { return res }
|
|
9
|
-
const TaintTrackingDummy = {
|
|
10
|
-
plusOperator: noop
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const TaintTracking = {
|
|
14
|
-
plusOperator: function (res, op1, op2) {
|
|
15
|
-
try {
|
|
16
|
-
if (typeof res !== 'string' ||
|
|
17
|
-
(typeof op1 !== 'string' && typeof op2 !== 'string')) { return res }
|
|
18
|
-
|
|
19
|
-
const store = storage.getStore()
|
|
20
|
-
const iastContext = iastContextFunctions.getIastContext(store)
|
|
21
|
-
const transactionId = iastContext && iastContext[IAST_TRANSACTION_ID]
|
|
22
|
-
if (transactionId) {
|
|
23
|
-
return TaintedUtils.concat(transactionId, res, op1, op2)
|
|
24
|
-
}
|
|
25
|
-
} catch (e) {
|
|
26
|
-
log.debug(e)
|
|
27
|
-
}
|
|
28
|
-
return res
|
|
29
|
-
}
|
|
30
|
-
}
|
|
2
|
+
const { IAST_TRANSACTION_ID } = require('../iast-context')
|
|
3
|
+
const { TaintTracking, TaintTrackingDummy } = require('./taint-tracking-impl')
|
|
31
4
|
|
|
32
5
|
function createTransaction (id, iastContext) {
|
|
33
6
|
if (id && iastContext) {
|