dd-trace 3.1.0 → 3.3.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 +4 -0
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +2 -1
- package/index.d.ts +43 -20
- package/package.json +6 -4
- package/packages/datadog-instrumentations/src/crypto.js +32 -0
- package/packages/datadog-instrumentations/src/grpc/server.js +15 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +136 -14
- package/packages/datadog-instrumentations/src/mocha.js +77 -31
- package/packages/datadog-instrumentations/src/net.js +13 -0
- package/packages/datadog-instrumentations/src/next.js +7 -3
- package/packages/datadog-plugin-jest/src/index.js +106 -6
- package/packages/datadog-plugin-mocha/src/index.js +15 -7
- package/packages/datadog-plugin-mongodb-core/src/index.js +19 -10
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +4 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/index.js +20 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +48 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +27 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
- package/packages/dd-trace/src/appsec/iast/iast-context.js +50 -0
- package/packages/dd-trace/src/appsec/iast/index.js +59 -0
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +94 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +70 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +121 -0
- package/packages/dd-trace/src/appsec/recommended.json +1144 -275
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -3
- package/packages/dd-trace/src/config.js +90 -10
- package/packages/dd-trace/src/constants.js +9 -1
- package/packages/dd-trace/src/encode/0.4.js +7 -1
- package/packages/dd-trace/src/encode/0.5.js +7 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +2 -2
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -20
- package/packages/dd-trace/src/encode/span-stats.js +155 -0
- package/packages/dd-trace/src/exporters/agent/index.js +14 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +6 -3
- package/packages/dd-trace/src/exporters/common/request.js +9 -5
- package/packages/dd-trace/src/exporters/span-stats/index.js +20 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +54 -0
- package/packages/dd-trace/src/format.js +2 -0
- package/packages/dd-trace/src/iitm.js +11 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +71 -0
- package/packages/dd-trace/src/opentracing/tracer.js +1 -1
- package/packages/dd-trace/src/plugin_manager.js +12 -2
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +16 -9
- package/packages/dd-trace/src/plugins/util/ip_blocklist.js +51 -0
- package/packages/dd-trace/src/plugins/util/web.js +100 -2
- package/packages/dd-trace/src/priority_sampler.js +36 -1
- package/packages/dd-trace/src/proxy.js +3 -0
- package/packages/dd-trace/src/ritm.js +10 -1
- package/packages/dd-trace/src/span_processor.js +7 -1
- package/packages/dd-trace/src/span_stats.js +210 -0
- package/packages/dd-trace/src/telemetry/dependencies.js +83 -0
- package/packages/dd-trace/src/{telemetry.js → telemetry/index.js} +10 -65
- package/packages/dd-trace/src/telemetry/send-data.js +35 -0
- package/packages/dd-trace/src/plugins/util/redis.js +0 -74
- package/packages/dd-trace/src/plugins/util/tx.js +0 -75
|
@@ -19,7 +19,31 @@ const patched = new WeakSet()
|
|
|
19
19
|
|
|
20
20
|
const testToAr = new WeakMap()
|
|
21
21
|
const originalFns = new WeakMap()
|
|
22
|
-
const
|
|
22
|
+
const testFileToSuiteAr = new Map()
|
|
23
|
+
|
|
24
|
+
function getSuitesByTestFile (root) {
|
|
25
|
+
const suitesByTestFile = {}
|
|
26
|
+
function getSuites (suite) {
|
|
27
|
+
if (suite.file) {
|
|
28
|
+
if (suitesByTestFile[suite.file]) {
|
|
29
|
+
suitesByTestFile[suite.file].push(suite)
|
|
30
|
+
} else {
|
|
31
|
+
suitesByTestFile[suite.file] = [suite]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
suite.suites.forEach(suite => {
|
|
35
|
+
getSuites(suite)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
getSuites(root)
|
|
39
|
+
|
|
40
|
+
const numSuitesByTestFile = Object.keys(suitesByTestFile).reduce((acc, testFile) => {
|
|
41
|
+
acc[testFile] = suitesByTestFile[testFile].length
|
|
42
|
+
return acc
|
|
43
|
+
}, {})
|
|
44
|
+
|
|
45
|
+
return { suitesByTestFile, numSuitesByTestFile }
|
|
46
|
+
}
|
|
23
47
|
|
|
24
48
|
function getTestStatus (test) {
|
|
25
49
|
if (test.pending) {
|
|
@@ -56,6 +80,8 @@ function mochaHook (Runner) {
|
|
|
56
80
|
return run.apply(this, arguments)
|
|
57
81
|
}
|
|
58
82
|
|
|
83
|
+
const { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
|
|
84
|
+
|
|
59
85
|
const testRunAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
60
86
|
|
|
61
87
|
this.once('end', testRunAsyncResource.bind(function () {
|
|
@@ -65,6 +91,7 @@ function mochaHook (Runner) {
|
|
|
65
91
|
} else if (this.failures !== 0) {
|
|
66
92
|
status = 'fail'
|
|
67
93
|
}
|
|
94
|
+
testFileToSuiteAr.clear()
|
|
68
95
|
testRunFinishCh.publish(status)
|
|
69
96
|
}))
|
|
70
97
|
|
|
@@ -75,34 +102,45 @@ function mochaHook (Runner) {
|
|
|
75
102
|
}))
|
|
76
103
|
|
|
77
104
|
this.on('suite', function (suite) {
|
|
78
|
-
if (suite.root) {
|
|
105
|
+
if (suite.root || !suite.tests.length) {
|
|
79
106
|
return
|
|
80
107
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
let asyncResource = testFileToSuiteAr.get(suite.file)
|
|
109
|
+
if (!asyncResource) {
|
|
110
|
+
asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
111
|
+
testFileToSuiteAr.set(suite.file, asyncResource)
|
|
112
|
+
asyncResource.runInAsyncScope(() => {
|
|
113
|
+
testSuiteStartCh.publish(suite)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
88
116
|
})
|
|
89
117
|
|
|
90
118
|
this.on('suite end', function (suite) {
|
|
91
119
|
if (suite.root) {
|
|
92
120
|
return
|
|
93
121
|
}
|
|
122
|
+
const suitesInTestFile = suitesByTestFile[suite.file]
|
|
123
|
+
|
|
124
|
+
const isLastSuite = --numSuitesByTestFile[suite.file] === 0
|
|
125
|
+
if (!isLastSuite) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
let status = 'pass'
|
|
95
|
-
if (suite.pending) {
|
|
130
|
+
if (suitesInTestFile.every(suite => suite.pending)) {
|
|
96
131
|
status = 'skip'
|
|
97
132
|
} else {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
133
|
+
// has to check every test in the test file
|
|
134
|
+
suitesInTestFile.forEach(suite => {
|
|
135
|
+
suite.eachTest(test => {
|
|
136
|
+
if (test.state === 'failed' || test.timedOut) {
|
|
137
|
+
status = 'fail'
|
|
138
|
+
}
|
|
139
|
+
})
|
|
102
140
|
})
|
|
103
141
|
}
|
|
104
142
|
|
|
105
|
-
const asyncResource =
|
|
143
|
+
const asyncResource = testFileToSuiteAr.get(suite.file)
|
|
106
144
|
asyncResource.runInAsyncScope(() => {
|
|
107
145
|
// get suite status
|
|
108
146
|
testSuiteFinishCh.publish(status)
|
|
@@ -125,7 +163,7 @@ function mochaHook (Runner) {
|
|
|
125
163
|
const status = getTestStatus(test)
|
|
126
164
|
|
|
127
165
|
// if there are afterEach to be run, we don't finish the test yet
|
|
128
|
-
if (!test.parent._afterEach.length) {
|
|
166
|
+
if (asyncResource && !test.parent._afterEach.length) {
|
|
129
167
|
asyncResource.runInAsyncScope(() => {
|
|
130
168
|
testFinishCh.publish(status)
|
|
131
169
|
})
|
|
@@ -148,34 +186,38 @@ function mochaHook (Runner) {
|
|
|
148
186
|
})
|
|
149
187
|
|
|
150
188
|
this.on('fail', (testOrHook, err) => {
|
|
189
|
+
const testFile = testOrHook.file
|
|
151
190
|
let test = testOrHook
|
|
152
191
|
const isHook = testOrHook.type === 'hook'
|
|
153
192
|
if (isHook && testOrHook.ctx) {
|
|
154
193
|
test = testOrHook.ctx.currentTest
|
|
155
194
|
}
|
|
156
|
-
let
|
|
195
|
+
let testAsyncResource
|
|
157
196
|
if (test) {
|
|
158
|
-
|
|
197
|
+
testAsyncResource = getTestAsyncResource(test)
|
|
159
198
|
}
|
|
160
|
-
if (
|
|
161
|
-
|
|
199
|
+
if (testAsyncResource) {
|
|
200
|
+
testAsyncResource.runInAsyncScope(() => {
|
|
162
201
|
if (isHook) {
|
|
163
|
-
err.message = `${testOrHook.
|
|
202
|
+
err.message = `${testOrHook.fullTitle()}: ${err.message}`
|
|
164
203
|
errorCh.publish(err)
|
|
165
204
|
// if it's a hook and it has failed, 'test end' will not be called
|
|
166
205
|
testFinishCh.publish('fail')
|
|
167
206
|
} else {
|
|
168
207
|
errorCh.publish(err)
|
|
169
208
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
const testSuiteAsyncResource = testFileToSuiteAr.get(testFile)
|
|
212
|
+
|
|
213
|
+
if (testSuiteAsyncResource) {
|
|
214
|
+
// we propagate the error to the suite
|
|
215
|
+
const testSuiteError = new Error(
|
|
216
|
+
`"${testOrHook.parent.fullTitle()}" failed with message "${err.message}"`
|
|
217
|
+
)
|
|
218
|
+
testSuiteError.stack = err.stack
|
|
219
|
+
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
220
|
+
testSuiteErrorCh.publish(testSuiteError)
|
|
179
221
|
})
|
|
180
222
|
}
|
|
181
223
|
})
|
|
@@ -189,7 +231,11 @@ function mochaHook (Runner) {
|
|
|
189
231
|
} else {
|
|
190
232
|
// if there is no async resource, the test has been skipped through `test.skip``
|
|
191
233
|
const skippedTestAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
192
|
-
|
|
234
|
+
if (test.fn) {
|
|
235
|
+
testToAr.set(test.fn, skippedTestAsyncResource)
|
|
236
|
+
} else {
|
|
237
|
+
testToAr.set(test, skippedTestAsyncResource)
|
|
238
|
+
}
|
|
193
239
|
skippedTestAsyncResource.runInAsyncScope(() => {
|
|
194
240
|
skipCh.publish(test)
|
|
195
241
|
})
|
|
@@ -49,6 +49,19 @@ addHook({ name: 'net' }, net => {
|
|
|
49
49
|
setupListeners(this, 'tcp', asyncResource)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
const emit = this.emit
|
|
53
|
+
this.emit = function (eventName) {
|
|
54
|
+
switch (eventName) {
|
|
55
|
+
case 'ready':
|
|
56
|
+
case 'connect':
|
|
57
|
+
return callbackResource.runInAsyncScope(() => {
|
|
58
|
+
return emit.apply(this, arguments)
|
|
59
|
+
})
|
|
60
|
+
default:
|
|
61
|
+
return emit.apply(this, arguments)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
try {
|
|
53
66
|
return connect.apply(this, arguments)
|
|
54
67
|
} catch (err) {
|
|
@@ -65,21 +65,25 @@ function wrapFindPageComponents (findPageComponents) {
|
|
|
65
65
|
const result = findPageComponents.apply(this, arguments)
|
|
66
66
|
|
|
67
67
|
if (result) {
|
|
68
|
-
pageLoadChannel.publish({ page: pathname })
|
|
68
|
+
pageLoadChannel.publish({ page: getPagePath(pathname) })
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
return result
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function getPagePath (page) {
|
|
76
|
+
return typeof page === 'object' ? page.pathname : page
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
function getPageFromPath (page, dynamicRoutes = []) {
|
|
76
80
|
for (const dynamicRoute of dynamicRoutes) {
|
|
77
81
|
if (dynamicRoute.page.startsWith('/api') && dynamicRoute.match(page)) {
|
|
78
|
-
return dynamicRoute.page
|
|
82
|
+
return getPagePath(dynamicRoute.page)
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
return page
|
|
86
|
+
return getPagePath(page)
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
function instrument (req, res, handler) {
|
|
@@ -9,10 +9,15 @@ const {
|
|
|
9
9
|
getTestEnvironmentMetadata,
|
|
10
10
|
getTestParentSpan,
|
|
11
11
|
getTestCommonTags,
|
|
12
|
+
getTestSessionCommonTags,
|
|
13
|
+
getTestSuiteCommonTags,
|
|
12
14
|
TEST_PARAMETERS,
|
|
13
15
|
getCodeOwnersFileEntries,
|
|
14
16
|
getCodeOwnersForFilename,
|
|
15
|
-
TEST_CODE_OWNERS
|
|
17
|
+
TEST_CODE_OWNERS,
|
|
18
|
+
TEST_SESSION_ID,
|
|
19
|
+
TEST_SUITE_ID,
|
|
20
|
+
TEST_COMMAND
|
|
16
21
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
17
22
|
|
|
18
23
|
// https://github.com/facebook/jest/blob/d6ad15b0f88a05816c2fe034dd6900d28315d570/packages/jest-worker/src/types.ts#L38
|
|
@@ -58,12 +63,93 @@ class JestPlugin extends Plugin {
|
|
|
58
63
|
this.testEnvironmentMetadata = getTestEnvironmentMetadata('jest', this.config)
|
|
59
64
|
this.codeOwnersEntries = getCodeOwnersFileEntries()
|
|
60
65
|
|
|
61
|
-
this.addSub('ci:jest:
|
|
66
|
+
this.addSub('ci:jest:session:start', (command) => {
|
|
67
|
+
if (!this.config.isAgentlessEnabled) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
const store = storage.getStore()
|
|
71
|
+
const childOf = getTestParentSpan(this.tracer)
|
|
72
|
+
const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
|
|
73
|
+
|
|
74
|
+
const testSessionSpan = this.tracer.startSpan('jest.test_session', {
|
|
75
|
+
childOf,
|
|
76
|
+
tags: {
|
|
77
|
+
...this.testEnvironmentMetadata,
|
|
78
|
+
...testSessionSpanMetadata
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
this.enter(testSessionSpan, store)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
this.addSub('ci:jest:session:finish', (status) => {
|
|
85
|
+
if (!this.config.isAgentlessEnabled) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
const testSessionSpan = storage.getStore().span
|
|
89
|
+
testSessionSpan.setTag(TEST_STATUS, status)
|
|
90
|
+
testSessionSpan.finish()
|
|
91
|
+
finishAllTraceSpans(testSessionSpan)
|
|
92
|
+
this.tracer._exporter._writer.flush()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Test suites can be run in a different process from jest's main one.
|
|
96
|
+
// This subscriber changes the configuration objects from jest to inject the trace id
|
|
97
|
+
// of the test session to the processes that run the test suites.
|
|
98
|
+
this.addSub('ci:jest:session:configuration', configs => {
|
|
99
|
+
if (!this.config.isAgentlessEnabled) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const testSessionSpan = storage.getStore().span
|
|
103
|
+
configs.forEach(config => {
|
|
104
|
+
config._ddTestSessionId = testSessionSpan.context()._traceId.toString(10)
|
|
105
|
+
config._ddTestCommand = testSessionSpan.context()._tags[TEST_COMMAND]
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
this.addSub('ci:jest:test-suite:start', ({ testSuite, testEnvironmentOptions }) => {
|
|
110
|
+
if (!this.config.isAgentlessEnabled) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { _ddTestSessionId: testSessionId, _ddTestCommand: testCommand } = testEnvironmentOptions
|
|
115
|
+
|
|
116
|
+
const store = storage.getStore()
|
|
117
|
+
|
|
118
|
+
const testSessionSpanContext = this.tracer.extract('text_map', {
|
|
119
|
+
'x-datadog-trace-id': testSessionId,
|
|
120
|
+
'x-datadog-parent-id': '0000000000000000'
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const testSuiteMetadata = getTestSuiteCommonTags(testCommand, this.tracer._version, testSuite)
|
|
124
|
+
|
|
125
|
+
const testSuiteSpan = this.tracer.startSpan('jest.test_suite', {
|
|
126
|
+
childOf: testSessionSpanContext,
|
|
127
|
+
tags: {
|
|
128
|
+
...this.testEnvironmentMetadata,
|
|
129
|
+
...testSuiteMetadata
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
this.enter(testSuiteSpan, store)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage }) => {
|
|
136
|
+
if (!this.config.isAgentlessEnabled) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
const testSuiteSpan = storage.getStore().span
|
|
140
|
+
testSuiteSpan.setTag(TEST_STATUS, status)
|
|
141
|
+
if (errorMessage) {
|
|
142
|
+
testSuiteSpan.setTag('error', new Error(errorMessage))
|
|
143
|
+
}
|
|
144
|
+
testSuiteSpan.finish()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
this.addSub('ci:jest:test-suite:code-coverage', (coverageFiles) => {
|
|
62
148
|
if (!this.config.isAgentlessEnabled || !this.config.isIntelligentTestRunnerEnabled) {
|
|
63
149
|
return
|
|
64
150
|
}
|
|
65
|
-
const
|
|
66
|
-
this.tracer._exporter.exportCoverage({
|
|
151
|
+
const testSuiteSpan = storage.getStore().span
|
|
152
|
+
this.tracer._exporter.exportCoverage({ span: testSuiteSpan, coverageFiles })
|
|
67
153
|
})
|
|
68
154
|
|
|
69
155
|
this.addSub('ci:jest:test:start', (test) => {
|
|
@@ -96,7 +182,20 @@ class JestPlugin extends Plugin {
|
|
|
96
182
|
}
|
|
97
183
|
|
|
98
184
|
startTestSpan (test) {
|
|
99
|
-
const
|
|
185
|
+
const suiteTags = {}
|
|
186
|
+
const store = storage.getStore()
|
|
187
|
+
const testSuiteSpan = store ? store.span : undefined
|
|
188
|
+
if (testSuiteSpan) {
|
|
189
|
+
const testSuiteId = testSuiteSpan.context()._spanId.toString(10)
|
|
190
|
+
suiteTags[TEST_SUITE_ID] = testSuiteId
|
|
191
|
+
suiteTags[TEST_SESSION_ID] = testSuiteSpan.context()._traceId.toString(10)
|
|
192
|
+
suiteTags[TEST_COMMAND] = testSuiteSpan.context()._tags[TEST_COMMAND]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const {
|
|
196
|
+
childOf,
|
|
197
|
+
...testSpanMetadata
|
|
198
|
+
} = getTestSpanMetadata(this.tracer, test)
|
|
100
199
|
|
|
101
200
|
const codeOwners = getCodeOwnersForFilename(test.suite, this.codeOwnersEntries)
|
|
102
201
|
|
|
@@ -109,7 +208,8 @@ class JestPlugin extends Plugin {
|
|
|
109
208
|
childOf,
|
|
110
209
|
tags: {
|
|
111
210
|
...this.testEnvironmentMetadata,
|
|
112
|
-
...testSpanMetadata
|
|
211
|
+
...testSpanMetadata,
|
|
212
|
+
...suiteTags
|
|
113
213
|
}
|
|
114
214
|
})
|
|
115
215
|
|
|
@@ -47,7 +47,7 @@ class MochaPlugin extends Plugin {
|
|
|
47
47
|
constructor (...args) {
|
|
48
48
|
super(...args)
|
|
49
49
|
|
|
50
|
-
this._testSuites = new
|
|
50
|
+
this._testSuites = new Map()
|
|
51
51
|
this._testNameToParams = {}
|
|
52
52
|
this.testEnvironmentMetadata = getTestEnvironmentMetadata('mocha', this.config)
|
|
53
53
|
this.sourceRoot = process.cwd()
|
|
@@ -75,7 +75,11 @@ class MochaPlugin extends Plugin {
|
|
|
75
75
|
return
|
|
76
76
|
}
|
|
77
77
|
const store = storage.getStore()
|
|
78
|
-
const testSuiteMetadata = getTestSuiteCommonTags(
|
|
78
|
+
const testSuiteMetadata = getTestSuiteCommonTags(
|
|
79
|
+
this.command,
|
|
80
|
+
this.tracer._version,
|
|
81
|
+
getTestSuitePath(suite.file, this.sourceRoot)
|
|
82
|
+
)
|
|
79
83
|
const testSuiteSpan = this.tracer.startSpan('mocha.test_suite', {
|
|
80
84
|
childOf: this.testSessionSpan,
|
|
81
85
|
tags: {
|
|
@@ -84,7 +88,7 @@ class MochaPlugin extends Plugin {
|
|
|
84
88
|
}
|
|
85
89
|
})
|
|
86
90
|
this.enter(testSuiteSpan, store)
|
|
87
|
-
this._testSuites.set(suite, testSuiteSpan)
|
|
91
|
+
this._testSuites.set(suite.file, testSuiteSpan)
|
|
88
92
|
})
|
|
89
93
|
|
|
90
94
|
this.addSub('ci:mocha:test-suite:finish', (status) => {
|
|
@@ -92,7 +96,10 @@ class MochaPlugin extends Plugin {
|
|
|
92
96
|
return
|
|
93
97
|
}
|
|
94
98
|
const span = storage.getStore().span
|
|
95
|
-
|
|
99
|
+
// the test status of the suite may have been set in ci:mocha:test-suite:error already
|
|
100
|
+
if (!span.context()._tags[TEST_STATUS]) {
|
|
101
|
+
span.setTag(TEST_STATUS, status)
|
|
102
|
+
}
|
|
96
103
|
span.finish()
|
|
97
104
|
})
|
|
98
105
|
|
|
@@ -102,6 +109,7 @@ class MochaPlugin extends Plugin {
|
|
|
102
109
|
}
|
|
103
110
|
const span = storage.getStore().span
|
|
104
111
|
span.setTag('error', err)
|
|
112
|
+
span.setTag(TEST_STATUS, 'fail')
|
|
105
113
|
})
|
|
106
114
|
|
|
107
115
|
this.addSub('ci:mocha:test:start', (test) => {
|
|
@@ -158,15 +166,15 @@ class MochaPlugin extends Plugin {
|
|
|
158
166
|
|
|
159
167
|
startTestSpan (test) {
|
|
160
168
|
const testSuiteTags = {}
|
|
161
|
-
const testSuiteSpan = this._testSuites.get(test.parent)
|
|
169
|
+
const testSuiteSpan = this._testSuites.get(test.parent.file)
|
|
162
170
|
|
|
163
171
|
if (testSuiteSpan) {
|
|
164
|
-
const testSuiteId = testSuiteSpan.context()._spanId.toString(
|
|
172
|
+
const testSuiteId = testSuiteSpan.context()._spanId.toString(10)
|
|
165
173
|
testSuiteTags[TEST_SUITE_ID] = testSuiteId
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
if (this.testSessionSpan) {
|
|
169
|
-
const testSessionId = this.testSessionSpan.context()._traceId.toString(
|
|
177
|
+
const testSessionId = this.testSessionSpan.context()._traceId.toString(10)
|
|
170
178
|
testSuiteTags[TEST_SESSION_ID] = testSessionId
|
|
171
179
|
testSuiteTags[TEST_COMMAND] = this.command
|
|
172
180
|
}
|
|
@@ -73,21 +73,21 @@ function truncate (input) {
|
|
|
73
73
|
return input.slice(0, Math.min(input.length, 10000))
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function simplify (input) {
|
|
77
|
-
return isBSON(input) ? input.toHexString() : input
|
|
78
|
-
}
|
|
79
|
-
|
|
80
76
|
function shouldSimplify (input) {
|
|
81
|
-
return !isObject(input)
|
|
77
|
+
return !isObject(input)
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
function shouldHide (input) {
|
|
85
|
-
return Buffer.isBuffer(input) || typeof input === 'function'
|
|
81
|
+
return Buffer.isBuffer(input) || typeof input === 'function' || isBinary(input)
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
function limitDepth (input) {
|
|
85
|
+
if (isBSON(input)) {
|
|
86
|
+
input = input.toJSON()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
89
|
if (shouldHide(input)) return '?'
|
|
90
|
-
if (shouldSimplify(input)) return
|
|
90
|
+
if (shouldSimplify(input)) return input
|
|
91
91
|
|
|
92
92
|
const output = {}
|
|
93
93
|
const queue = [{
|
|
@@ -104,11 +104,16 @@ function limitDepth (input) {
|
|
|
104
104
|
for (const key in input) {
|
|
105
105
|
if (typeof input[key] === 'function') continue
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
let child = input[key]
|
|
108
|
+
|
|
109
|
+
if (isBSON(child)) {
|
|
110
|
+
child = child.toJSON()
|
|
111
|
+
}
|
|
112
|
+
|
|
108
113
|
if (depth >= 10 || shouldHide(child)) {
|
|
109
114
|
output[key] = '?'
|
|
110
115
|
} else if (shouldSimplify(child)) {
|
|
111
|
-
output[key] =
|
|
116
|
+
output[key] = child
|
|
112
117
|
} else {
|
|
113
118
|
queue.push({
|
|
114
119
|
input: child,
|
|
@@ -127,7 +132,11 @@ function isObject (val) {
|
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
function isBSON (val) {
|
|
130
|
-
return val && val._bsontype
|
|
135
|
+
return val && val._bsontype && !isBinary(val)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isBinary (val) {
|
|
139
|
+
return val && val._bsontype === 'Binary'
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
module.exports = MongodbCorePlugin
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const analyzers = require('./analyzers')
|
|
4
|
+
|
|
5
|
+
function enableAllAnalyzers () {
|
|
6
|
+
for (const analyzer in analyzers) {
|
|
7
|
+
analyzers[analyzer].configure(true)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function disableAllAnalyzers () {
|
|
12
|
+
for (const analyzer in analyzers) {
|
|
13
|
+
analyzers[analyzer].configure(false)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
enableAllAnalyzers,
|
|
19
|
+
disableAllAnalyzers
|
|
20
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Plugin = require('../../../../src/plugins/plugin')
|
|
4
|
+
const { storage } = require('../../../../../datadog-core')
|
|
5
|
+
const { getFirstNonDDPathAndLine } = require('./../path-line')
|
|
6
|
+
const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
|
|
7
|
+
const { getIastContext } = require('../iast-context')
|
|
8
|
+
const overheadController = require('../overhead-controller')
|
|
9
|
+
|
|
10
|
+
class Analyzer extends Plugin {
|
|
11
|
+
constructor (type) {
|
|
12
|
+
super()
|
|
13
|
+
this._type = type
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_isVulnerable (value, context) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_report (value, context) {
|
|
21
|
+
const evidence = this._getEvidence(value)
|
|
22
|
+
const location = this._getLocation()
|
|
23
|
+
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
24
|
+
const vulnerability = createVulnerability(this._type, evidence, spanId, location)
|
|
25
|
+
addVulnerability(context, vulnerability)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_getEvidence (value) {
|
|
29
|
+
return { value }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_getLocation () {
|
|
33
|
+
return getFirstNonDDPathAndLine()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
analyze (value) {
|
|
37
|
+
const iastContext = getIastContext(storage.getStore())
|
|
38
|
+
if (iastContext && this._isVulnerable(value, iastContext) && this._checkOCE(iastContext)) {
|
|
39
|
+
this._report(value, iastContext)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_checkOCE (context) {
|
|
44
|
+
return overheadController.hasQuota(overheadController.OPERATIONS.REPORT_VULNERABILITY, context)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Analyzer
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const Analyzer = require('./vulnerability-analyzer')
|
|
3
|
+
|
|
4
|
+
const INSECURE_CIPHERS = new Set([
|
|
5
|
+
'des', 'des-cbc', 'des-cfb', 'des-cfb1', 'des-cfb8', 'des-ecb', 'des-ede', 'des-ede-cbc', 'des-ede-cfb',
|
|
6
|
+
'des-ede-ecb', 'des-ede-ofb', 'des-ede3', 'des-ede3-cbc', 'des-ede3-cfb', 'des-ede3-cfb1', 'des-ede3-cfb8',
|
|
7
|
+
'des-ede3-ecb', 'des-ede3-ofb', 'des-ofb', 'des3', 'des3-wrap',
|
|
8
|
+
'rc2', 'rc2-128', 'rc2-40', 'rc2-40-cbc', 'rc2-64', 'rc2-64-cbc', 'rc2-cbc', 'rc2-cfb', 'rc2-ecb', 'rc2-ofb',
|
|
9
|
+
'blowfish',
|
|
10
|
+
'rc4', 'rc4-40', 'rc4-hmac-md5'
|
|
11
|
+
].map(algorithm => algorithm.toLowerCase()))
|
|
12
|
+
|
|
13
|
+
class WeakCipherAnalyzer extends Analyzer {
|
|
14
|
+
constructor () {
|
|
15
|
+
super('WEAK_CIPHER')
|
|
16
|
+
this.addSub('datadog:crypto:cipher:start', ({ algorithm }) => this.analyze(algorithm))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_isVulnerable (algorithm) {
|
|
20
|
+
if (algorithm && typeof algorithm === 'string') {
|
|
21
|
+
return INSECURE_CIPHERS.has(algorithm.toLowerCase())
|
|
22
|
+
}
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = new WeakCipherAnalyzer()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const Analyzer = require('./vulnerability-analyzer')
|
|
3
|
+
|
|
4
|
+
const INSECURE_HASH_ALGORITHMS = new Set([
|
|
5
|
+
'md4', 'md4WithRSAEncryption', 'RSA-MD4',
|
|
6
|
+
'RSA-MD5', 'md5', 'md5-sha1', 'ssl3-md5', 'md5WithRSAEncryption',
|
|
7
|
+
'RSA-SHA1', 'RSA-SHA1-2', 'sha1', 'md5-sha1', 'sha1WithRSAEncryption', 'ssl3-sha1'
|
|
8
|
+
].map(algorithm => algorithm.toLowerCase()))
|
|
9
|
+
|
|
10
|
+
class WeakHashAnalyzer extends Analyzer {
|
|
11
|
+
constructor () {
|
|
12
|
+
super('WEAK_HASH')
|
|
13
|
+
this.addSub('datadog:crypto:hashing:start', ({ algorithm }) => this.analyze(algorithm))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_isVulnerable (algorithm) {
|
|
17
|
+
if (typeof algorithm === 'string') {
|
|
18
|
+
return INSECURE_HASH_ALGORITHMS.has(algorithm.toLowerCase())
|
|
19
|
+
}
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = new WeakHashAnalyzer()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const IAST_CONTEXT_KEY = Symbol('_dd.iast.context')
|
|
2
|
+
|
|
3
|
+
function getIastContext (store) {
|
|
4
|
+
return store && store[IAST_CONTEXT_KEY]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* TODO Fix storage problem when the close event is called without
|
|
8
|
+
finish event to remove `topContext` references
|
|
9
|
+
We have to save the context in two places, because
|
|
10
|
+
clean can be called when the storage store is not available
|
|
11
|
+
*/
|
|
12
|
+
function saveIastContext (store, topContext, context) {
|
|
13
|
+
if (store && topContext) {
|
|
14
|
+
store[IAST_CONTEXT_KEY] = context
|
|
15
|
+
topContext[IAST_CONTEXT_KEY] = context
|
|
16
|
+
return store[IAST_CONTEXT_KEY]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* TODO Fix storage problem when the close event is called without
|
|
21
|
+
finish event to remove `topContext` references
|
|
22
|
+
iastContext is currently saved in store and request rootContext
|
|
23
|
+
to fix problems with `close` without `finish` events
|
|
24
|
+
*/
|
|
25
|
+
function cleanIastContext (store, context, iastContext) {
|
|
26
|
+
if (store) {
|
|
27
|
+
if (!iastContext) {
|
|
28
|
+
iastContext = store[IAST_CONTEXT_KEY]
|
|
29
|
+
}
|
|
30
|
+
store[IAST_CONTEXT_KEY] = null
|
|
31
|
+
}
|
|
32
|
+
if (context) {
|
|
33
|
+
if (!iastContext) {
|
|
34
|
+
iastContext = context[IAST_CONTEXT_KEY]
|
|
35
|
+
}
|
|
36
|
+
context[IAST_CONTEXT_KEY] = null
|
|
37
|
+
}
|
|
38
|
+
if (iastContext) {
|
|
39
|
+
Object.keys(iastContext).forEach(key => delete iastContext[key])
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
getIastContext,
|
|
47
|
+
saveIastContext,
|
|
48
|
+
cleanIastContext,
|
|
49
|
+
IAST_CONTEXT_KEY
|
|
50
|
+
}
|