dd-trace 4.36.0 → 4.38.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/README.md +8 -1
- package/ci/init.js +7 -0
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/index.d.ts +21 -6
- package/init.js +27 -3
- package/package.json +6 -6
- package/packages/datadog-esbuild/index.js +8 -2
- package/packages/datadog-instrumentations/src/aws-sdk.js +4 -1
- package/packages/datadog-instrumentations/src/cucumber.js +182 -105
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/lodash.js +31 -0
- package/packages/datadog-instrumentations/src/playwright.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/services/index.js +3 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sfn.js +7 -0
- package/packages/datadog-plugin-aws-sdk/src/services/states.js +7 -0
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +64 -0
- package/packages/datadog-plugin-cucumber/src/index.js +83 -11
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +4 -0
- package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -33
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +3 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +55 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/hardcoded-password-analyzer.js +13 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +8 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +6 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +56 -0
- package/packages/dd-trace/src/ci-visibility/exporters/{jest-worker → test-worker}/writer.js +7 -0
- package/packages/dd-trace/src/config.js +25 -8
- package/packages/dd-trace/src/encode/0.4.js +50 -3
- package/packages/dd-trace/src/exporter.js +2 -1
- package/packages/dd-trace/src/plugins/database.js +20 -5
- package/packages/dd-trace/src/plugins/util/test.js +7 -0
- package/packages/dd-trace/src/profiling/profiler.js +23 -7
- package/packages/dd-trace/src/proxy.js +7 -1
- package/packages/dd-trace/src/serverless.js +3 -5
- package/packages/dd-trace/src/telemetry/index.js +9 -3
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +42 -1
- package/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js +0 -33
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { channel, addHook } = require('./helpers/instrument')
|
|
4
|
+
|
|
5
|
+
const shimmer = require('../../datadog-shimmer')
|
|
6
|
+
|
|
7
|
+
addHook({ name: 'lodash', versions: ['>=4'] }, lodash => {
|
|
8
|
+
const lodashOperationCh = channel('datadog:lodash:operation')
|
|
9
|
+
|
|
10
|
+
const instrumentedLodashFn = ['trim', 'trimStart', 'trimEnd', 'toLower', 'toUpper', 'join']
|
|
11
|
+
|
|
12
|
+
shimmer.massWrap(
|
|
13
|
+
lodash,
|
|
14
|
+
instrumentedLodashFn,
|
|
15
|
+
lodashFn => {
|
|
16
|
+
return function () {
|
|
17
|
+
if (!lodashOperationCh.hasSubscribers) {
|
|
18
|
+
return lodashFn.apply(this, arguments)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = lodashFn.apply(this, arguments)
|
|
22
|
+
const message = { operation: lodashFn.name, arguments, result }
|
|
23
|
+
lodashOperationCh.publish(message)
|
|
24
|
+
|
|
25
|
+
return message.result
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return lodash
|
|
31
|
+
})
|
|
@@ -297,7 +297,12 @@ function dispatcherRunWrapper (run) {
|
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
function dispatcherRunWrapperNew (run) {
|
|
300
|
-
return function () {
|
|
300
|
+
return function (testGroups) {
|
|
301
|
+
if (!this._allTests) {
|
|
302
|
+
// Removed in https://github.com/microsoft/playwright/commit/1e52c37b254a441cccf332520f60225a5acc14c7
|
|
303
|
+
// Not available from >=1.44.0
|
|
304
|
+
this._allTests = testGroups.map(g => g.tests).flat()
|
|
305
|
+
}
|
|
301
306
|
remainingTestsByFile = getTestsBySuiteFromTestGroups(arguments[0])
|
|
302
307
|
return run.apply(this, arguments)
|
|
303
308
|
}
|
|
@@ -7,6 +7,9 @@ exports.kinesis = require('./kinesis')
|
|
|
7
7
|
exports.lambda = require('./lambda')
|
|
8
8
|
exports.redshift = require('./redshift')
|
|
9
9
|
exports.s3 = require('./s3')
|
|
10
|
+
exports.sfn = require('./sfn')
|
|
10
11
|
exports.sns = require('./sns')
|
|
11
12
|
exports.sqs = require('./sqs')
|
|
13
|
+
exports.states = require('./states')
|
|
14
|
+
exports.stepfunctions = require('./stepfunctions')
|
|
12
15
|
exports.default = require('./default')
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const log = require('../../../dd-trace/src/log')
|
|
3
|
+
const BaseAwsSdkPlugin = require('../base')
|
|
4
|
+
|
|
5
|
+
class Stepfunctions extends BaseAwsSdkPlugin {
|
|
6
|
+
static get id () { return 'stepfunctions' }
|
|
7
|
+
|
|
8
|
+
// This is the shape of StartExecutionInput, as defined in
|
|
9
|
+
// https://github.com/aws/aws-sdk-js/blob/master/apis/states-2016-11-23.normal.json
|
|
10
|
+
// "StartExecutionInput": {
|
|
11
|
+
// "type": "structure",
|
|
12
|
+
// "required": [
|
|
13
|
+
// "stateMachineArn"
|
|
14
|
+
// ],
|
|
15
|
+
// "members": {
|
|
16
|
+
// "stateMachineArn": {
|
|
17
|
+
// "shape": "Arn",
|
|
18
|
+
// },
|
|
19
|
+
// "name": {
|
|
20
|
+
// "shape": "Name",
|
|
21
|
+
// },
|
|
22
|
+
// "input": {
|
|
23
|
+
// "shape": "SensitiveData",
|
|
24
|
+
// },
|
|
25
|
+
// "traceHeader": {
|
|
26
|
+
// "shape": "TraceHeader",
|
|
27
|
+
// }
|
|
28
|
+
// }
|
|
29
|
+
|
|
30
|
+
generateTags (params, operation, response) {
|
|
31
|
+
if (!params) return {}
|
|
32
|
+
const tags = { 'resource.name': params.name ? `${operation} ${params.name}` : `${operation}` }
|
|
33
|
+
if (operation === 'startExecution' || operation === 'startSyncExecution') {
|
|
34
|
+
tags.statemachinearn = `${params.stateMachineArn}`
|
|
35
|
+
}
|
|
36
|
+
return tags
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
requestInject (span, request) {
|
|
40
|
+
const operation = request.operation
|
|
41
|
+
if (operation === 'startExecution' || operation === 'startSyncExecution') {
|
|
42
|
+
if (!request.params || !request.params.input) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const input = request.params.input
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const inputObj = JSON.parse(input)
|
|
50
|
+
if (inputObj && typeof inputObj === 'object') {
|
|
51
|
+
// We've parsed the input JSON string
|
|
52
|
+
inputObj._datadog = {}
|
|
53
|
+
this.tracer.inject(span, 'text_map', inputObj._datadog)
|
|
54
|
+
const newInput = JSON.stringify(inputObj)
|
|
55
|
+
request.params.input = newInput
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
log.info('Unable to treat input as JSON')
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = Stepfunctions
|
|
@@ -18,7 +18,14 @@ const {
|
|
|
18
18
|
TEST_SOURCE_FILE,
|
|
19
19
|
TEST_EARLY_FLAKE_ENABLED,
|
|
20
20
|
TEST_IS_NEW,
|
|
21
|
-
TEST_IS_RETRY
|
|
21
|
+
TEST_IS_RETRY,
|
|
22
|
+
TEST_SUITE_ID,
|
|
23
|
+
TEST_SESSION_ID,
|
|
24
|
+
TEST_COMMAND,
|
|
25
|
+
TEST_MODULE,
|
|
26
|
+
TEST_MODULE_ID,
|
|
27
|
+
TEST_SUITE,
|
|
28
|
+
CUCUMBER_IS_PARALLEL
|
|
22
29
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
23
30
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
24
31
|
const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
|
|
@@ -32,6 +39,22 @@ const {
|
|
|
32
39
|
TELEMETRY_ITR_UNSKIPPABLE,
|
|
33
40
|
TELEMETRY_CODE_COVERAGE_NUM_FILES
|
|
34
41
|
} = require('../../dd-trace/src/ci-visibility/telemetry')
|
|
42
|
+
const id = require('../../dd-trace/src/id')
|
|
43
|
+
|
|
44
|
+
const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID
|
|
45
|
+
|
|
46
|
+
function getTestSuiteTags (testSuiteSpan) {
|
|
47
|
+
const suiteTags = {
|
|
48
|
+
[TEST_SUITE_ID]: testSuiteSpan.context().toSpanId(),
|
|
49
|
+
[TEST_SESSION_ID]: testSuiteSpan.context().toTraceId(),
|
|
50
|
+
[TEST_COMMAND]: testSuiteSpan.context()._tags[TEST_COMMAND],
|
|
51
|
+
[TEST_MODULE]: 'cucumber'
|
|
52
|
+
}
|
|
53
|
+
if (testSuiteSpan.context()._parentId) {
|
|
54
|
+
suiteTags[TEST_MODULE_ID] = testSuiteSpan.context()._parentId.toString(10)
|
|
55
|
+
}
|
|
56
|
+
return suiteTags
|
|
57
|
+
}
|
|
35
58
|
|
|
36
59
|
class CucumberPlugin extends CiPlugin {
|
|
37
60
|
static get id () {
|
|
@@ -43,6 +66,8 @@ class CucumberPlugin extends CiPlugin {
|
|
|
43
66
|
|
|
44
67
|
this.sourceRoot = process.cwd()
|
|
45
68
|
|
|
69
|
+
this.testSuiteSpanByPath = {}
|
|
70
|
+
|
|
46
71
|
this.addSub('ci:cucumber:session:finish', ({
|
|
47
72
|
status,
|
|
48
73
|
isSuitesSkipped,
|
|
@@ -50,7 +75,8 @@ class CucumberPlugin extends CiPlugin {
|
|
|
50
75
|
testCodeCoverageLinesTotal,
|
|
51
76
|
hasUnskippableSuites,
|
|
52
77
|
hasForcedToRunSuites,
|
|
53
|
-
isEarlyFlakeDetectionEnabled
|
|
78
|
+
isEarlyFlakeDetectionEnabled,
|
|
79
|
+
isParallel
|
|
54
80
|
}) => {
|
|
55
81
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
|
|
56
82
|
addIntelligentTestRunnerSpanTags(
|
|
@@ -70,6 +96,9 @@ class CucumberPlugin extends CiPlugin {
|
|
|
70
96
|
if (isEarlyFlakeDetectionEnabled) {
|
|
71
97
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
|
|
72
98
|
}
|
|
99
|
+
if (isParallel) {
|
|
100
|
+
this.testSessionSpan.setTag(CUCUMBER_IS_PARALLEL, 'true')
|
|
101
|
+
}
|
|
73
102
|
|
|
74
103
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
75
104
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
@@ -101,7 +130,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
101
130
|
if (itrCorrelationId) {
|
|
102
131
|
testSuiteMetadata[ITR_CORRELATION_ID] = itrCorrelationId
|
|
103
132
|
}
|
|
104
|
-
|
|
133
|
+
const testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
|
|
105
134
|
childOf: this.testModuleSpan,
|
|
106
135
|
tags: {
|
|
107
136
|
[COMPONENT]: this.constructor.id,
|
|
@@ -109,25 +138,29 @@ class CucumberPlugin extends CiPlugin {
|
|
|
109
138
|
...testSuiteMetadata
|
|
110
139
|
}
|
|
111
140
|
})
|
|
141
|
+
this.testSuiteSpanByPath[testSuitePath] = testSuiteSpan
|
|
142
|
+
|
|
112
143
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
113
144
|
if (this.libraryConfig?.isCodeCoverageEnabled) {
|
|
114
145
|
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
|
|
115
146
|
}
|
|
116
147
|
})
|
|
117
148
|
|
|
118
|
-
this.addSub('ci:cucumber:test-suite:finish', status => {
|
|
119
|
-
this.
|
|
120
|
-
|
|
149
|
+
this.addSub('ci:cucumber:test-suite:finish', ({ status, testSuitePath }) => {
|
|
150
|
+
const testSuiteSpan = this.testSuiteSpanByPath[testSuitePath]
|
|
151
|
+
testSuiteSpan.setTag(TEST_STATUS, status)
|
|
152
|
+
testSuiteSpan.finish()
|
|
121
153
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
122
154
|
})
|
|
123
155
|
|
|
124
|
-
this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
|
|
156
|
+
this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile, testSuitePath }) => {
|
|
125
157
|
if (!this.libraryConfig?.isCodeCoverageEnabled) {
|
|
126
158
|
return
|
|
127
159
|
}
|
|
128
160
|
if (!coverageFiles.length) {
|
|
129
161
|
this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
|
|
130
162
|
}
|
|
163
|
+
const testSuiteSpan = this.testSuiteSpanByPath[testSuitePath]
|
|
131
164
|
|
|
132
165
|
const relativeCoverageFiles = [...coverageFiles, suiteFile]
|
|
133
166
|
.map(filename => getTestSuitePath(filename, this.repositoryRoot))
|
|
@@ -135,8 +168,8 @@ class CucumberPlugin extends CiPlugin {
|
|
|
135
168
|
this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
|
|
136
169
|
|
|
137
170
|
const formattedCoverage = {
|
|
138
|
-
sessionId:
|
|
139
|
-
suiteId:
|
|
171
|
+
sessionId: testSuiteSpan.context()._traceId,
|
|
172
|
+
suiteId: testSuiteSpan.context()._spanId,
|
|
140
173
|
files: relativeCoverageFiles
|
|
141
174
|
}
|
|
142
175
|
|
|
@@ -144,7 +177,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
144
177
|
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
|
|
145
178
|
})
|
|
146
179
|
|
|
147
|
-
this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine }) => {
|
|
180
|
+
this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine, isParallel }) => {
|
|
148
181
|
const store = storage.getStore()
|
|
149
182
|
const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
|
|
150
183
|
const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
|
|
@@ -153,6 +186,10 @@ class CucumberPlugin extends CiPlugin {
|
|
|
153
186
|
[TEST_SOURCE_START]: testSourceLine,
|
|
154
187
|
[TEST_SOURCE_FILE]: testSourceFile
|
|
155
188
|
}
|
|
189
|
+
if (isParallel) {
|
|
190
|
+
extraTags[CUCUMBER_IS_PARALLEL] = 'true'
|
|
191
|
+
}
|
|
192
|
+
|
|
156
193
|
const testSpan = this.startTestSpan(testName, testSuite, extraTags)
|
|
157
194
|
|
|
158
195
|
this.enter(testSpan, store)
|
|
@@ -172,6 +209,36 @@ class CucumberPlugin extends CiPlugin {
|
|
|
172
209
|
this.enter(span, store)
|
|
173
210
|
})
|
|
174
211
|
|
|
212
|
+
this.addSub('ci:cucumber:worker-report:trace', (traces) => {
|
|
213
|
+
const formattedTraces = JSON.parse(traces).map(trace =>
|
|
214
|
+
trace.map(span => ({
|
|
215
|
+
...span,
|
|
216
|
+
span_id: id(span.span_id),
|
|
217
|
+
trace_id: id(span.trace_id),
|
|
218
|
+
parent_id: id(span.parent_id)
|
|
219
|
+
}))
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
// We have to update the test session, test module and test suite ids
|
|
223
|
+
// before we export them in the main process
|
|
224
|
+
formattedTraces.forEach(trace => {
|
|
225
|
+
trace.forEach(span => {
|
|
226
|
+
if (span.name === 'cucumber.test') {
|
|
227
|
+
const testSuite = span.meta[TEST_SUITE]
|
|
228
|
+
const testSuiteSpan = this.testSuiteSpanByPath[testSuite]
|
|
229
|
+
|
|
230
|
+
const testSuiteTags = getTestSuiteTags(testSuiteSpan)
|
|
231
|
+
span.meta = {
|
|
232
|
+
...span.meta,
|
|
233
|
+
...testSuiteTags
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
this.tracer._exporter.export(trace)
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
175
242
|
this.addSub('ci:cucumber:test:finish', ({ isStep, status, skipReason, errorMessage, isNew, isEfdRetry }) => {
|
|
176
243
|
const span = storage.getStore().span
|
|
177
244
|
const statusTag = isStep ? 'step.status' : TEST_STATUS
|
|
@@ -201,6 +268,10 @@ class CucumberPlugin extends CiPlugin {
|
|
|
201
268
|
{ hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
|
|
202
269
|
)
|
|
203
270
|
finishAllTraceSpans(span)
|
|
271
|
+
// If it's a worker, flushing is cheap, as it's just sending data to the main process
|
|
272
|
+
if (isCucumberWorker) {
|
|
273
|
+
this.tracer._exporter.flush()
|
|
274
|
+
}
|
|
204
275
|
}
|
|
205
276
|
})
|
|
206
277
|
|
|
@@ -213,10 +284,11 @@ class CucumberPlugin extends CiPlugin {
|
|
|
213
284
|
}
|
|
214
285
|
|
|
215
286
|
startTestSpan (testName, testSuite, extraTags) {
|
|
287
|
+
const testSuiteSpan = this.testSuiteSpanByPath[testSuite]
|
|
216
288
|
return super.startTestSpan(
|
|
217
289
|
testName,
|
|
218
290
|
testSuite,
|
|
219
|
-
|
|
291
|
+
testSuiteSpan,
|
|
220
292
|
extraTags
|
|
221
293
|
)
|
|
222
294
|
}
|
|
@@ -9,6 +9,10 @@ class HardcodedPasswordAnalyzer extends HardcodedBaseAnalyzer {
|
|
|
9
9
|
constructor () {
|
|
10
10
|
super(HARDCODED_PASSWORD, allRules)
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
_getEvidence (value) {
|
|
14
|
+
return { value: `${value.ident}` }
|
|
15
|
+
}
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
module.exports = new HardcodedPasswordAnalyzer()
|
|
@@ -2,35 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const dc = require('dc-polyfill')
|
|
4
4
|
const log = require('../../log')
|
|
5
|
-
const { calculateDDBasePath } = require('../../util')
|
|
6
5
|
|
|
7
6
|
const telemetryLog = dc.channel('datadog:telemetry:log')
|
|
8
7
|
|
|
9
|
-
const ddBasePath = calculateDDBasePath(__dirname)
|
|
10
|
-
const EOL = '\n'
|
|
11
|
-
const STACK_FRAME_LINE_REGEX = /^\s*at\s/gm
|
|
12
|
-
|
|
13
|
-
function sanitize (logEntry, stack) {
|
|
14
|
-
if (!stack) return logEntry
|
|
15
|
-
|
|
16
|
-
let stackLines = stack.split(EOL)
|
|
17
|
-
|
|
18
|
-
const firstIndex = stackLines.findIndex(l => l.match(STACK_FRAME_LINE_REGEX))
|
|
19
|
-
|
|
20
|
-
const isDDCode = firstIndex > -1 && stackLines[firstIndex].includes(ddBasePath)
|
|
21
|
-
stackLines = stackLines
|
|
22
|
-
.filter((line, index) => (isDDCode && index < firstIndex) || line.includes(ddBasePath))
|
|
23
|
-
.map(line => line.replace(ddBasePath, ''))
|
|
24
|
-
|
|
25
|
-
logEntry.stack_trace = stackLines.join(EOL)
|
|
26
|
-
|
|
27
|
-
if (!isDDCode) {
|
|
28
|
-
logEntry.message = 'omitted'
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return logEntry
|
|
32
|
-
}
|
|
33
|
-
|
|
34
8
|
function getTelemetryLog (data, level) {
|
|
35
9
|
try {
|
|
36
10
|
data = typeof data === 'function' ? data() : data
|
|
@@ -42,18 +16,13 @@ function getTelemetryLog (data, level) {
|
|
|
42
16
|
message = String(data.message || data)
|
|
43
17
|
}
|
|
44
18
|
|
|
45
|
-
|
|
19
|
+
const logEntry = {
|
|
46
20
|
message,
|
|
47
21
|
level
|
|
48
22
|
}
|
|
49
|
-
|
|
50
23
|
if (data.stack) {
|
|
51
|
-
logEntry =
|
|
52
|
-
if (logEntry.stack_trace === '') {
|
|
53
|
-
return
|
|
54
|
-
}
|
|
24
|
+
logEntry.stack_trace = data.stack
|
|
55
25
|
}
|
|
56
|
-
|
|
57
26
|
return logEntry
|
|
58
27
|
} catch (e) {
|
|
59
28
|
log.error(e)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const csiMethods = [
|
|
4
4
|
{ src: 'concat' },
|
|
5
|
+
{ src: 'join' },
|
|
5
6
|
{ src: 'parse' },
|
|
6
7
|
{ src: 'plusOperator', operator: true },
|
|
7
8
|
{ src: 'random' },
|
|
@@ -9,6 +10,8 @@ const csiMethods = [
|
|
|
9
10
|
{ src: 'slice' },
|
|
10
11
|
{ src: 'substr' },
|
|
11
12
|
{ src: 'substring' },
|
|
13
|
+
{ src: 'toLowerCase', dst: 'stringCase' },
|
|
14
|
+
{ src: 'toUpperCase', dst: 'stringCase' },
|
|
12
15
|
{ src: 'trim' },
|
|
13
16
|
{ src: 'trimEnd' },
|
|
14
17
|
{ src: 'trimStart', dst: 'trim' }
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const dc = require('dc-polyfill')
|
|
3
4
|
const TaintedUtils = require('@datadog/native-iast-taint-tracking')
|
|
4
5
|
const { IAST_TRANSACTION_ID } = require('../iast-context')
|
|
5
6
|
const iastTelemetry = require('../telemetry')
|
|
6
7
|
const { REQUEST_TAINTED } = require('../telemetry/iast-metric')
|
|
7
8
|
const { isInfoAllowed } = require('../telemetry/verbosity')
|
|
8
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
getTaintTrackingImpl,
|
|
11
|
+
getTaintTrackingNoop,
|
|
12
|
+
lodashTaintTrackingHandler
|
|
13
|
+
} = require('./taint-tracking-impl')
|
|
9
14
|
const { taintObject } = require('./operations-taint-object')
|
|
10
15
|
|
|
16
|
+
const lodashOperationCh = dc.channel('datadog:lodash:operation')
|
|
17
|
+
|
|
11
18
|
function createTransaction (id, iastContext) {
|
|
12
19
|
if (id && iastContext) {
|
|
13
20
|
iastContext[IAST_TRANSACTION_ID] = TaintedUtils.createTransaction(id)
|
|
@@ -92,10 +99,12 @@ function enableTaintOperations (telemetryVerbosity) {
|
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
global._ddiast = getTaintTrackingImpl(telemetryVerbosity)
|
|
102
|
+
lodashOperationCh.subscribe(lodashTaintTrackingHandler)
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
function disableTaintOperations () {
|
|
98
106
|
global._ddiast = getTaintTrackingNoop()
|
|
107
|
+
lodashOperationCh.unsubscribe(lodashTaintTrackingHandler)
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
function setMaxTransactions (transactions) {
|
|
@@ -18,6 +18,7 @@ function noop (res) { return res }
|
|
|
18
18
|
// Otherwise you may end up rewriting a method and not providing its rewritten implementation
|
|
19
19
|
const TaintTrackingNoop = {
|
|
20
20
|
concat: noop,
|
|
21
|
+
join: noop,
|
|
21
22
|
parse: noop,
|
|
22
23
|
plusOperator: noop,
|
|
23
24
|
random: noop,
|
|
@@ -25,6 +26,7 @@ const TaintTrackingNoop = {
|
|
|
25
26
|
slice: noop,
|
|
26
27
|
substr: noop,
|
|
27
28
|
substring: noop,
|
|
29
|
+
stringCase: noop,
|
|
28
30
|
trim: noop,
|
|
29
31
|
trimEnd: noop
|
|
30
32
|
}
|
|
@@ -113,6 +115,13 @@ function csiMethodsOverrides (getContext) {
|
|
|
113
115
|
return res
|
|
114
116
|
},
|
|
115
117
|
|
|
118
|
+
stringCase: getCsiFn(
|
|
119
|
+
(transactionId, res, target) => TaintedUtils.stringCase(transactionId, res, target),
|
|
120
|
+
getContext,
|
|
121
|
+
String.prototype.toLowerCase,
|
|
122
|
+
String.prototype.toUpperCase
|
|
123
|
+
),
|
|
124
|
+
|
|
116
125
|
trim: getCsiFn(
|
|
117
126
|
(transactionId, res, target) => TaintedUtils.trim(transactionId, res, target),
|
|
118
127
|
getContext,
|
|
@@ -147,6 +156,22 @@ function csiMethodsOverrides (getContext) {
|
|
|
147
156
|
}
|
|
148
157
|
}
|
|
149
158
|
|
|
159
|
+
return res
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
join: function (res, fn, target, separator) {
|
|
163
|
+
if (fn === Array.prototype.join) {
|
|
164
|
+
try {
|
|
165
|
+
const iastContext = getContext()
|
|
166
|
+
const transactionId = getTransactionId(iastContext)
|
|
167
|
+
if (transactionId) {
|
|
168
|
+
res = TaintedUtils.arrayJoin(transactionId, res, target, separator)
|
|
169
|
+
}
|
|
170
|
+
} catch (e) {
|
|
171
|
+
iastLog.error(e)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
150
175
|
return res
|
|
151
176
|
}
|
|
152
177
|
}
|
|
@@ -176,7 +201,36 @@ function getTaintTrackingNoop () {
|
|
|
176
201
|
return getTaintTrackingImpl(null, true)
|
|
177
202
|
}
|
|
178
203
|
|
|
204
|
+
const lodashFns = {
|
|
205
|
+
join: TaintedUtils.arrayJoin,
|
|
206
|
+
toLower: TaintedUtils.stringCase,
|
|
207
|
+
toUpper: TaintedUtils.stringCase,
|
|
208
|
+
trim: TaintedUtils.trim,
|
|
209
|
+
trimEnd: TaintedUtils.trimEnd,
|
|
210
|
+
trimStart: TaintedUtils.trim
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getLodashTaintedUtilFn (lodashFn) {
|
|
215
|
+
return lodashFns[lodashFn] || ((transactionId, result) => result)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function lodashTaintTrackingHandler (message) {
|
|
219
|
+
try {
|
|
220
|
+
if (!message.result) return
|
|
221
|
+
const context = getContextDefault()
|
|
222
|
+
const transactionId = getTransactionId(context)
|
|
223
|
+
if (transactionId) {
|
|
224
|
+
message.result = getLodashTaintedUtilFn(message.operation)(transactionId, message.result, ...message.arguments)
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
iastLog.error(`Error invoking CSI lodash ${message.operation}`)
|
|
228
|
+
.errorAndPublish(e)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
179
232
|
module.exports = {
|
|
180
233
|
getTaintTrackingImpl,
|
|
181
|
-
getTaintTrackingNoop
|
|
234
|
+
getTaintTrackingNoop,
|
|
235
|
+
lodashTaintTrackingHandler
|
|
182
236
|
}
|
|
@@ -6,6 +6,7 @@ const vulnerabilities = require('../../vulnerabilities')
|
|
|
6
6
|
const { contains, intersects, remove } = require('./range-utils')
|
|
7
7
|
|
|
8
8
|
const commandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
|
|
9
|
+
const hardcodedPasswordAnalyzer = require('./sensitive-analyzers/hardcoded-password-analyzer')
|
|
9
10
|
const headerSensitiveAnalyzer = require('./sensitive-analyzers/header-sensitive-analyzer')
|
|
10
11
|
const jsonSensitiveAnalyzer = require('./sensitive-analyzers/json-sensitive-analyzer')
|
|
11
12
|
const ldapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
|
|
@@ -31,6 +32,9 @@ class SensitiveHandler {
|
|
|
31
32
|
this._sensitiveAnalyzers.set(vulnerabilities.HEADER_INJECTION, (evidence) => {
|
|
32
33
|
return headerSensitiveAnalyzer(evidence, this._namePattern, this._valuePattern)
|
|
33
34
|
})
|
|
35
|
+
this._sensitiveAnalyzers.set(vulnerabilities.HARDCODED_PASSWORD, (evidence) => {
|
|
36
|
+
return hardcodedPasswordAnalyzer(evidence, this._valuePattern)
|
|
37
|
+
})
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
isSensibleName (name) {
|
|
@@ -51,7 +55,9 @@ class SensitiveHandler {
|
|
|
51
55
|
const sensitiveAnalyzer = this._sensitiveAnalyzers.get(vulnerabilityType)
|
|
52
56
|
if (sensitiveAnalyzer) {
|
|
53
57
|
const sensitiveRanges = sensitiveAnalyzer(evidence)
|
|
54
|
-
|
|
58
|
+
if (evidence.ranges || sensitiveRanges?.length) {
|
|
59
|
+
return this.toRedactedJson(evidence, sensitiveRanges, sourcesIndexes, sources)
|
|
60
|
+
}
|
|
55
61
|
}
|
|
56
62
|
return null
|
|
57
63
|
}
|
|
@@ -67,7 +73,7 @@ class SensitiveHandler {
|
|
|
67
73
|
let nextTaintedIndex = 0
|
|
68
74
|
let sourceIndex
|
|
69
75
|
|
|
70
|
-
let nextTainted = ranges
|
|
76
|
+
let nextTainted = ranges?.shift()
|
|
71
77
|
let nextSensitive = sensitive.shift()
|
|
72
78
|
|
|
73
79
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -49,6 +49,10 @@ class VulnerabilityFormatter {
|
|
|
49
49
|
evidence.ranges = ranges
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
if (!evidence.ranges) {
|
|
53
|
+
return { value: evidence.value }
|
|
54
|
+
}
|
|
55
|
+
|
|
52
56
|
evidence.ranges.forEach((range, rangeIndex) => {
|
|
53
57
|
if (fromIndex < range.start) {
|
|
54
58
|
valueParts.push({ value: evidence.value.substring(fromIndex, range.start) })
|
|
@@ -65,12 +69,8 @@ class VulnerabilityFormatter {
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
formatEvidence (type, evidence, sourcesIndexes, sources) {
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
return undefined
|
|
71
|
-
} else {
|
|
72
|
-
return { value: evidence.value }
|
|
73
|
-
}
|
|
72
|
+
if (typeof evidence.value === 'undefined') {
|
|
73
|
+
return undefined
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
return this._redactVulnearbilities
|