dd-trace 4.36.0 → 4.37.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/ci/init.js +7 -0
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/index.d.ts +14 -6
- package/package.json +4 -4
- 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/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 +7 -0
- 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/telemetry/index.js +9 -3
- package/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js +0 -33
|
@@ -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,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
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Writer = require('./writer')
|
|
4
|
+
const {
|
|
5
|
+
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
6
|
+
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
7
|
+
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
|
|
8
|
+
} = require('../../../plugins/util/test')
|
|
9
|
+
|
|
10
|
+
function getInterprocessTraceCode () {
|
|
11
|
+
if (process.env.JEST_WORKER_ID) {
|
|
12
|
+
return JEST_WORKER_TRACE_PAYLOAD_CODE
|
|
13
|
+
}
|
|
14
|
+
if (process.env.CUCUMBER_WORKER_ID) {
|
|
15
|
+
return CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// TODO: make it available with cucumber
|
|
21
|
+
function getInterprocessCoverageCode () {
|
|
22
|
+
if (process.env.JEST_WORKER_ID) {
|
|
23
|
+
return JEST_WORKER_COVERAGE_PAYLOAD_CODE
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Lightweight exporter whose writers only do simple JSON serialization
|
|
30
|
+
* of trace and coverage payloads, which they send to the test framework's main process.
|
|
31
|
+
* Currently used by Jest and Cucumber workers.
|
|
32
|
+
*/
|
|
33
|
+
class TestWorkerCiVisibilityExporter {
|
|
34
|
+
constructor () {
|
|
35
|
+
const interprocessTraceCode = getInterprocessTraceCode()
|
|
36
|
+
const interprocessCoverageCode = getInterprocessCoverageCode()
|
|
37
|
+
|
|
38
|
+
this._writer = new Writer(interprocessTraceCode)
|
|
39
|
+
this._coverageWriter = new Writer(interprocessCoverageCode)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export (payload) {
|
|
43
|
+
this._writer.append(payload)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exportCoverage (formattedCoverage) {
|
|
47
|
+
this._coverageWriter.append(formattedCoverage)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
flush () {
|
|
51
|
+
this._writer.flush()
|
|
52
|
+
this._coverageWriter.flush()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = TestWorkerCiVisibilityExporter
|
|
@@ -23,11 +23,18 @@ class Writer {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
_sendPayload (data) {
|
|
26
|
+
// ## Jest
|
|
26
27
|
// Only available when `child_process` is used for the jest worker.
|
|
27
28
|
// eslint-disable-next-line
|
|
28
29
|
// https://github.com/facebook/jest/blob/bb39cb2c617a3334bf18daeca66bd87b7ccab28b/packages/jest-worker/README.md#experimental-worker
|
|
29
30
|
// If worker_threads is used, this will not work
|
|
30
31
|
// TODO: make it compatible with worker_threads
|
|
32
|
+
|
|
33
|
+
// ## Cucumber
|
|
34
|
+
// This reports to the test's main process the same way test data is reported by Cucumber
|
|
35
|
+
// See cucumber code:
|
|
36
|
+
// eslint-disable-next-line
|
|
37
|
+
// https://github.com/cucumber/cucumber-js/blob/5ce371870b677fe3d1a14915dc535688946f734c/src/runtime/parallel/run_worker.ts#L13
|
|
31
38
|
if (process.send) { // it only works if process.send is available
|
|
32
39
|
process.send([this._interprocessCode, data])
|
|
33
40
|
}
|
|
@@ -446,6 +446,7 @@ class Config {
|
|
|
446
446
|
this._setValue(defaults, 'appsec.obfuscatorValueRegex', defaultWafObfuscatorValueRegex)
|
|
447
447
|
this._setValue(defaults, 'appsec.rateLimit', 100)
|
|
448
448
|
this._setValue(defaults, 'appsec.rules', undefined)
|
|
449
|
+
this._setValue(defaults, 'appsec.sca.enabled', null)
|
|
449
450
|
this._setValue(defaults, 'appsec.wafTimeout', 5e3) // µs
|
|
450
451
|
this._setValue(defaults, 'clientIpEnabled', false)
|
|
451
452
|
this._setValue(defaults, 'clientIpHeader', null)
|
|
@@ -516,6 +517,7 @@ class Config {
|
|
|
516
517
|
this._setValue(defaults, 'tracing', true)
|
|
517
518
|
this._setValue(defaults, 'url', undefined)
|
|
518
519
|
this._setValue(defaults, 'version', pkg.version)
|
|
520
|
+
this._setValue(defaults, 'instrumentation_config_id', undefined)
|
|
519
521
|
}
|
|
520
522
|
|
|
521
523
|
_applyEnvironment () {
|
|
@@ -528,6 +530,7 @@ class Config {
|
|
|
528
530
|
DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
|
|
529
531
|
DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
|
|
530
532
|
DD_APPSEC_RULES,
|
|
533
|
+
DD_APPSEC_SCA_ENABLED,
|
|
531
534
|
DD_APPSEC_TRACE_RATE_LIMIT,
|
|
532
535
|
DD_APPSEC_WAF_TIMEOUT,
|
|
533
536
|
DD_DATA_STREAMS_ENABLED,
|
|
@@ -547,6 +550,7 @@ class Config {
|
|
|
547
550
|
DD_IAST_REQUEST_SAMPLING,
|
|
548
551
|
DD_IAST_TELEMETRY_VERBOSITY,
|
|
549
552
|
DD_INSTRUMENTATION_TELEMETRY_ENABLED,
|
|
553
|
+
DD_INSTRUMENTATION_CONFIG_ID,
|
|
550
554
|
DD_LOGS_INJECTION,
|
|
551
555
|
DD_OPENAI_LOGS_ENABLED,
|
|
552
556
|
DD_OPENAI_SPAN_CHAR_LIMIT,
|
|
@@ -615,6 +619,8 @@ class Config {
|
|
|
615
619
|
this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
|
|
616
620
|
this._setValue(env, 'appsec.rateLimit', maybeInt(DD_APPSEC_TRACE_RATE_LIMIT))
|
|
617
621
|
this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
|
|
622
|
+
// DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
|
|
623
|
+
this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
|
|
618
624
|
this._setValue(env, 'appsec.wafTimeout', maybeInt(DD_APPSEC_WAF_TIMEOUT))
|
|
619
625
|
this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
|
|
620
626
|
this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
|
|
@@ -695,6 +701,7 @@ class Config {
|
|
|
695
701
|
DD_INSTRUMENTATION_TELEMETRY_ENABLED, // to comply with instrumentation telemetry specs
|
|
696
702
|
!(this._isInServerlessEnvironment() || JEST_WORKER_ID)
|
|
697
703
|
))
|
|
704
|
+
this._setString(env, 'instrumentation_config_id', DD_INSTRUMENTATION_CONFIG_ID)
|
|
698
705
|
this._setBoolean(env, 'telemetry.debug', DD_TELEMETRY_DEBUG)
|
|
699
706
|
this._setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED)
|
|
700
707
|
this._setValue(env, 'telemetry.heartbeatInterval', maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000)))
|
|
@@ -18,7 +18,8 @@ module.exports = name => {
|
|
|
18
18
|
case exporters.AGENT_PROXY:
|
|
19
19
|
return require('./ci-visibility/exporters/agent-proxy')
|
|
20
20
|
case exporters.JEST_WORKER:
|
|
21
|
-
|
|
21
|
+
case exporters.CUCUMBER_WORKER:
|
|
22
|
+
return require('./ci-visibility/exporters/test-worker')
|
|
22
23
|
default:
|
|
23
24
|
return inAWSLambda && !usingLambdaExtension ? require('./exporters/log') : require('./exporters/agent')
|
|
24
25
|
}
|