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.
Files changed (32) hide show
  1. package/ci/init.js +7 -0
  2. package/ext/exporters.d.ts +2 -1
  3. package/ext/exporters.js +2 -1
  4. package/index.d.ts +14 -6
  5. package/package.json +4 -4
  6. package/packages/datadog-esbuild/index.js +8 -2
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +4 -1
  8. package/packages/datadog-instrumentations/src/cucumber.js +182 -105
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  10. package/packages/datadog-instrumentations/src/lodash.js +31 -0
  11. package/packages/datadog-instrumentations/src/playwright.js +6 -1
  12. package/packages/datadog-plugin-aws-sdk/src/services/index.js +3 -0
  13. package/packages/datadog-plugin-aws-sdk/src/services/sfn.js +7 -0
  14. package/packages/datadog-plugin-aws-sdk/src/services/states.js +7 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +64 -0
  16. package/packages/datadog-plugin-cucumber/src/index.js +83 -11
  17. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -0
  18. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +4 -0
  19. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +3 -0
  20. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -1
  21. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +55 -1
  22. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/hardcoded-password-analyzer.js +13 -0
  23. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +8 -2
  24. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +6 -6
  25. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +56 -0
  26. package/packages/dd-trace/src/ci-visibility/exporters/{jest-worker → test-worker}/writer.js +7 -0
  27. package/packages/dd-trace/src/config.js +7 -0
  28. package/packages/dd-trace/src/exporter.js +2 -1
  29. package/packages/dd-trace/src/plugins/database.js +20 -5
  30. package/packages/dd-trace/src/plugins/util/test.js +7 -0
  31. package/packages/dd-trace/src/telemetry/index.js +9 -3
  32. 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
- this.testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
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.testSuiteSpan.setTag(TEST_STATUS, status)
120
- this.testSuiteSpan.finish()
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: this.testSuiteSpan.context()._traceId,
139
- suiteId: this.testSuiteSpan.context()._spanId,
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
- this.testSuiteSpan,
291
+ testSuiteSpan,
220
292
  extraTags
221
293
  )
222
294
  }
@@ -48,6 +48,7 @@ class HardcodedBaseAnalyzer extends Analyzer {
48
48
  file,
49
49
  line: match.location.line,
50
50
  column: match.location.column,
51
+ ident: match.location.ident,
51
52
  data: match.ruleId
52
53
  }))
53
54
  }
@@ -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 { getTaintTrackingImpl, getTaintTrackingNoop } = require('./taint-tracking-impl')
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
  }
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ module.exports = function extractSensitiveRanges (evidence, valuePattern) {
4
+ const { value } = evidence
5
+ if (valuePattern.test(value)) {
6
+ return [{
7
+ start: 0,
8
+ end: value.length
9
+ }]
10
+ }
11
+
12
+ return []
13
+ }
@@ -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
- return this.toRedactedJson(evidence, sensitiveRanges, sourcesIndexes, sources)
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.shift()
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 (!evidence.ranges && !evidence.rangesToApply) {
69
- if (typeof evidence.value === 'undefined') {
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
- return require('./ci-visibility/exporters/jest-worker')
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
  }