dd-trace 5.2.0 → 5.4.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 (86) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +1 -32
  3. package/ci/init.js +1 -4
  4. package/index.d.ts +21 -0
  5. package/package.json +7 -6
  6. package/packages/datadog-instrumentations/src/amqplib.js +1 -1
  7. package/packages/datadog-instrumentations/src/child_process.js +150 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +12 -12
  9. package/packages/datadog-instrumentations/src/express.js +20 -0
  10. package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
  12. package/packages/datadog-instrumentations/src/jest.js +149 -11
  13. package/packages/datadog-instrumentations/src/mocha.js +142 -16
  14. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  15. package/packages/datadog-instrumentations/src/next.js +17 -3
  16. package/packages/datadog-instrumentations/src/playwright.js +41 -9
  17. package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
  18. package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +107 -1
  20. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  21. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  22. package/packages/datadog-plugin-cucumber/src/index.js +16 -11
  23. package/packages/datadog-plugin-cypress/src/plugin.js +52 -23
  24. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  25. package/packages/datadog-plugin-http/src/client.js +1 -1
  26. package/packages/datadog-plugin-jest/src/index.js +43 -6
  27. package/packages/datadog-plugin-kafkajs/src/consumer.js +16 -0
  28. package/packages/datadog-plugin-mocha/src/index.js +47 -17
  29. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  30. package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
  31. package/packages/datadog-plugin-rhea/src/producer.js +11 -0
  32. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  33. package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
  34. package/packages/dd-trace/src/appsec/channels.js +2 -1
  35. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  36. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  37. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  38. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  39. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  40. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +12 -1
  41. package/packages/dd-trace/src/appsec/iast/index.js +4 -4
  42. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  45. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  46. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  47. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  48. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  49. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  50. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +29 -2
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  52. package/packages/dd-trace/src/appsec/index.js +17 -2
  53. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  54. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  55. package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
  56. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
  57. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  58. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  59. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
  60. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
  62. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
  63. package/packages/dd-trace/src/config.js +22 -9
  64. package/packages/dd-trace/src/datastreams/processor.js +6 -0
  65. package/packages/dd-trace/src/datastreams/writer.js +2 -5
  66. package/packages/dd-trace/src/dogstatsd.js +3 -5
  67. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
  68. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  69. package/packages/dd-trace/src/format.js +25 -1
  70. package/packages/dd-trace/src/noop/span.js +1 -0
  71. package/packages/dd-trace/src/opentelemetry/span.js +9 -2
  72. package/packages/dd-trace/src/opentracing/span.js +38 -0
  73. package/packages/dd-trace/src/opentracing/span_context.js +12 -6
  74. package/packages/dd-trace/src/opentracing/tracer.js +2 -1
  75. package/packages/dd-trace/src/plugins/ci_plugin.js +25 -9
  76. package/packages/dd-trace/src/plugins/index.js +1 -0
  77. package/packages/dd-trace/src/plugins/util/git.js +6 -0
  78. package/packages/dd-trace/src/plugins/util/test.js +53 -8
  79. package/packages/dd-trace/src/profiling/config.js +22 -22
  80. package/packages/dd-trace/src/proxy.js +31 -23
  81. package/packages/dd-trace/src/span_processor.js +5 -1
  82. package/packages/dd-trace/src/telemetry/index.js +6 -0
  83. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  84. package/packages/dd-trace/src/telemetry/send-data.js +0 -3
  85. package/packages/datadog-instrumentations/src/child-process.js +0 -29
  86. package/packages/dd-trace/src/plugins/util/exec.js +0 -34
@@ -73,14 +73,41 @@ function getRootDir (playwrightRunner) {
73
73
  if (playwrightRunner._configDir) {
74
74
  return playwrightRunner._configDir
75
75
  }
76
- if (playwrightRunner._config && playwrightRunner._config.config) {
77
- return playwrightRunner._config.config.rootDir
76
+ if (playwrightRunner._config) {
77
+ return playwrightRunner._config.config?.rootDir || process.cwd()
78
78
  }
79
79
  return process.cwd()
80
80
  }
81
81
 
82
- function testBeginHandler (test) {
83
- const { _requireFile: testSuiteAbsolutePath, title: testName, _type, location: { line: testSourceLine } } = test
82
+ function getProjectsFromRunner (runner) {
83
+ const config = getPlaywrightConfig(runner)
84
+ return config.projects?.map(({ project }) => project)
85
+ }
86
+
87
+ function getProjectsFromDispatcher (dispatcher) {
88
+ const newConfig = dispatcher._config?.config?.projects
89
+ if (newConfig) {
90
+ return newConfig
91
+ }
92
+ // old
93
+ return dispatcher._loader?.fullConfig()?.projects
94
+ }
95
+
96
+ function getBrowserNameFromProjects (projects, projectId) {
97
+ if (!projects) {
98
+ return null
99
+ }
100
+ return projects.find(project =>
101
+ project.__projectId === projectId || project._id === projectId
102
+ )?.name
103
+ }
104
+
105
+ function testBeginHandler (test, browserName) {
106
+ const {
107
+ _requireFile: testSuiteAbsolutePath,
108
+ title: testName, _type,
109
+ location: { line: testSourceLine }
110
+ } = test
84
111
 
85
112
  if (_type === 'beforeAll' || _type === 'afterAll') {
86
113
  return
@@ -100,7 +127,7 @@ function testBeginHandler (test) {
100
127
  const testAsyncResource = new AsyncResource('bound-anonymous-fn')
101
128
  testToAr.set(test, testAsyncResource)
102
129
  testAsyncResource.runInAsyncScope(() => {
103
- testStartCh.publish({ testName, testSuiteAbsolutePath, testSourceLine })
130
+ testStartCh.publish({ testName, testSuiteAbsolutePath, testSourceLine, browserName })
104
131
  })
105
132
  }
106
133
 
@@ -166,11 +193,12 @@ function dispatcherHook (dispatcherExport) {
166
193
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
167
194
  const dispatcher = this
168
195
  const worker = createWorker.apply(this, arguments)
169
-
170
196
  worker.process.on('message', ({ method, params }) => {
171
197
  if (method === 'testBegin') {
172
198
  const { test } = dispatcher._testById.get(params.testId)
173
- testBeginHandler(test)
199
+ const projects = getProjectsFromDispatcher(dispatcher)
200
+ const browser = getBrowserNameFromProjects(projects, test._projectId)
201
+ testBeginHandler(test, browser)
174
202
  } else if (method === 'testEnd') {
175
203
  const { test } = dispatcher._testById.get(params.testId)
176
204
 
@@ -203,7 +231,9 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
203
231
 
204
232
  worker.on('testBegin', ({ testId }) => {
205
233
  const test = getTestByTestId(dispatcher, testId)
206
- testBeginHandler(test)
234
+ const projects = getProjectsFromDispatcher(dispatcher)
235
+ const browser = getBrowserNameFromProjects(projects, test._projectId)
236
+ testBeginHandler(test, browser)
207
237
  })
208
238
  worker.on('testEnd', ({ testId, status, errors, annotations }) => {
209
239
  const test = getTestByTestId(dispatcher, testId)
@@ -226,6 +256,7 @@ function runnerHook (runnerExport, playwrightVersion) {
226
256
  testSessionAsyncResource.runInAsyncScope(() => {
227
257
  testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
228
258
  })
259
+ const projects = getProjectsFromRunner(this)
229
260
 
230
261
  const runAllTestsReturn = await runAllTests.apply(this, arguments)
231
262
 
@@ -234,7 +265,8 @@ function runnerHook (runnerExport, playwrightVersion) {
234
265
  // there were tests that did not go through `testBegin` or `testEnd`,
235
266
  // because they were skipped
236
267
  tests.forEach(test => {
237
- testBeginHandler(test)
268
+ const browser = getBrowserNameFromProjects(projects, test._projectId)
269
+ testBeginHandler(test, browser)
238
270
  testEndHandler(test, [], 'skip')
239
271
  })
240
272
  })
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { TEXT_MAP } = require('../../../ext/formats')
4
4
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
5
+ const { getAmqpMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
5
6
  const { getResourceName } = require('./util')
6
7
 
7
8
  class AmqplibConsumerPlugin extends ConsumerPlugin {
@@ -13,7 +14,7 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
13
14
 
14
15
  const childOf = extract(this.tracer, message)
15
16
 
16
- this.startSpan({
17
+ const span = this.startSpan({
17
18
  childOf,
18
19
  resource: getResourceName(method, fields),
19
20
  type: 'worker',
@@ -26,6 +27,14 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
26
27
  'amqp.destination': fields.destination
27
28
  }
28
29
  })
30
+
31
+ if (this.config.dsmEnabled && message) {
32
+ const payloadSize = getAmqpMessageSize({ headers: message.properties.headers, content: message.content })
33
+ const queue = fields.queue ?? fields.routingKey
34
+ this.tracer.decodeDataStreamsContext(message.properties.headers[CONTEXT_PROPAGATION_KEY])
35
+ this.tracer
36
+ .setCheckpoint(['direction:in', `topic:${queue}`, 'type:rabbitmq'], span, payloadSize)
37
+ }
29
38
  }
30
39
  }
31
40
 
@@ -3,13 +3,15 @@
3
3
  const { TEXT_MAP } = require('../../../ext/formats')
4
4
  const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
5
5
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
6
+ const { encodePathwayContext } = require('../../dd-trace/src/datastreams/pathway')
7
+ const { getAmqpMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
6
8
  const { getResourceName } = require('./util')
7
9
 
8
10
  class AmqplibProducerPlugin extends ProducerPlugin {
9
11
  static get id () { return 'amqplib' }
10
12
  static get operation () { return 'command' }
11
13
 
12
- start ({ channel = {}, method, fields }) {
14
+ start ({ channel = {}, method, fields, message }) {
13
15
  if (method !== 'basic.publish') return
14
16
 
15
17
  const stream = (channel.connection && channel.connection.stream) || {}
@@ -30,6 +32,17 @@ class AmqplibProducerPlugin extends ProducerPlugin {
30
32
  fields.headers = fields.headers || {}
31
33
 
32
34
  this.tracer.inject(span, TEXT_MAP, fields.headers)
35
+
36
+ if (this.config.dsmEnabled) {
37
+ const hasRoutingKey = fields.routingKey != null
38
+ const payloadSize = getAmqpMessageSize({ content: message, headers: fields.headers })
39
+ const dataStreamsContext = this.tracer
40
+ .setCheckpoint(
41
+ ['direction:out', `exchange:${fields.exchange}`, `has_routing_key:${hasRoutingKey}`, 'type:rabbitmq']
42
+ , span, payloadSize)
43
+ const pathwayCtx = encodePathwayContext(dataStreamsContext)
44
+ fields.headers[CONTEXT_PROPAGATION_KEY] = pathwayCtx
45
+ }
33
46
  }
34
47
  }
35
48
 
@@ -1,15 +1,69 @@
1
1
  'use strict'
2
2
  const {
3
- CONTEXT_PROPAGATION_KEY
3
+ CONTEXT_PROPAGATION_KEY,
4
+ getSizeOrZero
4
5
  } = require('../../../dd-trace/src/datastreams/processor')
5
6
  const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway')
6
7
  const log = require('../../../dd-trace/src/log')
7
8
  const BaseAwsSdkPlugin = require('../base')
9
+ const { storage } = require('../../../datadog-core')
8
10
 
9
11
  class Kinesis extends BaseAwsSdkPlugin {
10
12
  static get id () { return 'kinesis' }
11
13
  static get peerServicePrecursors () { return ['streamname'] }
12
14
 
15
+ constructor (...args) {
16
+ super(...args)
17
+
18
+ // TODO(bengl) Find a way to create the response span tags without this WeakMap being populated
19
+ // in the base class
20
+ this.requestTags = new WeakMap()
21
+
22
+ this.addSub('apm:aws:response:start:kinesis', obj => {
23
+ const { request, response } = obj
24
+ const store = storage.getStore()
25
+ const plugin = this
26
+
27
+ // if we have either of these operations, we want to store the streamName param
28
+ // since it is not typically available during get/put records requests
29
+ if (request.operation === 'getShardIterator' || request.operation === 'listShards') {
30
+ this.storeStreamName(request.params, request.operation, store)
31
+ return
32
+ }
33
+
34
+ if (request.operation === 'getRecords') {
35
+ let span
36
+ const responseExtraction = this.responseExtract(request.params, request.operation, response)
37
+ if (responseExtraction && responseExtraction.maybeChildOf) {
38
+ obj.needsFinish = true
39
+ const options = {
40
+ childOf: responseExtraction.maybeChildOf,
41
+ tags: Object.assign(
42
+ {},
43
+ this.requestTags.get(request) || {},
44
+ { 'span.kind': 'server' }
45
+ )
46
+ }
47
+ span = plugin.tracer.startSpan('aws.response', options)
48
+ this.enter(span, store)
49
+ }
50
+
51
+ // get the stream name that should have been stored previously
52
+ const { streamName } = storage.getStore()
53
+
54
+ // extract DSM context after as we might not have a parent-child but may have a DSM context
55
+ this.responseExtractDSMContext(
56
+ request.operation, response, span ?? null, streamName
57
+ )
58
+ }
59
+ })
60
+
61
+ this.addSub('apm:aws:response:finish:kinesis', err => {
62
+ const { span } = storage.getStore()
63
+ this.finish(span, null, err)
64
+ })
65
+ }
66
+
13
67
  generateTags (params, operation, response) {
14
68
  if (!params || !params.StreamName) return {}
15
69
 
@@ -20,6 +74,58 @@ class Kinesis extends BaseAwsSdkPlugin {
20
74
  }
21
75
  }
22
76
 
77
+ storeStreamName (params, operation, store) {
78
+ if (!operation || (operation !== 'getShardIterator' && operation !== 'listShards')) return
79
+ if (!params || !params.StreamName) return
80
+
81
+ const streamName = params.StreamName
82
+ storage.enterWith({ ...store, streamName })
83
+ }
84
+
85
+ responseExtract (params, operation, response) {
86
+ if (operation !== 'getRecords') return
87
+ if (params.Limit && params.Limit !== 1) return
88
+ if (!response || !response.Records || !response.Records[0]) return
89
+
90
+ const record = response.Records[0]
91
+
92
+ try {
93
+ const decodedData = JSON.parse(Buffer.from(record.Data).toString())
94
+
95
+ return {
96
+ maybeChildOf: this.tracer.extract('text_map', decodedData._datadog),
97
+ parsedAttributes: decodedData._datadog
98
+ }
99
+ } catch (e) {
100
+ log.error(e)
101
+ }
102
+ }
103
+
104
+ responseExtractDSMContext (operation, response, span, streamName) {
105
+ if (!this.config.dsmEnabled) return
106
+ if (operation !== 'getRecords') return
107
+ if (!response || !response.Records || !response.Records[0]) return
108
+
109
+ // we only want to set the payloadSize on the span if we have one message, not repeatedly
110
+ span = response.Records.length > 1 ? null : span
111
+
112
+ response.Records.forEach(record => {
113
+ const parsedAttributes = JSON.parse(Buffer.from(record.Data).toString())
114
+
115
+ if (
116
+ parsedAttributes &&
117
+ parsedAttributes._datadog &&
118
+ parsedAttributes._datadog[CONTEXT_PROPAGATION_KEY] &&
119
+ streamName
120
+ ) {
121
+ const payloadSize = getSizeOrZero(record.Data)
122
+ this.tracer.decodeDataStreamsContext(Buffer.from(parsedAttributes._datadog[CONTEXT_PROPAGATION_KEY]))
123
+ this.tracer
124
+ .setCheckpoint(['direction:in', `topic:${streamName}`, 'type:kinesis'], span, payloadSize)
125
+ }
126
+ })
127
+ }
128
+
23
129
  // AWS-SDK will b64 kinesis payloads
24
130
  // or will accept an already b64 encoded payload
25
131
  // This method handles both
@@ -0,0 +1,91 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const scrubChildProcessCmd = require('./scrub-cmd-params')
5
+
6
+ const MAX_ARG_SIZE = 4096 // 4kB
7
+
8
+ function truncateCommand (cmdFields) {
9
+ let size = cmdFields[0].length
10
+ let truncated = false
11
+ for (let i = 1; i < cmdFields.length; i++) {
12
+ if (size >= MAX_ARG_SIZE) {
13
+ truncated = true
14
+ cmdFields[i] = ''
15
+ continue
16
+ }
17
+
18
+ const argLen = cmdFields[i].length
19
+ if (size < MAX_ARG_SIZE && size + argLen > MAX_ARG_SIZE) {
20
+ cmdFields[i] = cmdFields[i].substring(0, 2)
21
+ truncated = true
22
+ }
23
+
24
+ size += argLen
25
+ }
26
+
27
+ return truncated
28
+ }
29
+
30
+ class ChildProcessPlugin extends TracingPlugin {
31
+ static get id () { return 'child_process' }
32
+ static get prefix () { return 'tracing:datadog:child_process:execution' }
33
+
34
+ get tracer () {
35
+ return this._tracer
36
+ }
37
+
38
+ start ({ command, shell }) {
39
+ if (typeof command !== 'string') {
40
+ return
41
+ }
42
+
43
+ const cmdFields = scrubChildProcessCmd(command)
44
+ const truncated = truncateCommand(cmdFields)
45
+ const property = (shell === true) ? 'cmd.shell' : 'cmd.exec'
46
+
47
+ const meta = {
48
+ 'component': 'subprocess',
49
+ [property]: (shell === true) ? cmdFields.join(' ') : JSON.stringify(cmdFields)
50
+ }
51
+
52
+ if (truncated) {
53
+ meta['cmd.truncated'] = `${truncated}`
54
+ }
55
+
56
+ this.startSpan('command_execution', {
57
+ service: this.config.service,
58
+ resource: (shell === true) ? 'sh' : cmdFields[0],
59
+ type: 'system',
60
+ meta
61
+ })
62
+ }
63
+
64
+ end ({ result, error }) {
65
+ let exitCode
66
+
67
+ if (result !== undefined) {
68
+ exitCode = result?.status || 0
69
+ } else if (error !== undefined) {
70
+ exitCode = error?.status || error?.code || 0
71
+ } else {
72
+ // TracingChannels call start, end synchronously. Later when the promise is resolved then asyncStart asyncEnd.
73
+ // Therefore in the case of calling end with neither result nor error means that they will come in the asyncEnd.
74
+ return
75
+ }
76
+
77
+ this.activeSpan?.setTag('cmd.exit_code', `${exitCode}`)
78
+ this.activeSpan?.finish()
79
+ }
80
+
81
+ error (error) {
82
+ this.addError(error)
83
+ }
84
+
85
+ asyncEnd ({ result }) {
86
+ this.activeSpan?.setTag('cmd.exit_code', `${result}`)
87
+ this.activeSpan?.finish()
88
+ }
89
+ }
90
+
91
+ module.exports = ChildProcessPlugin
@@ -0,0 +1,125 @@
1
+ 'use strict'
2
+
3
+ const shellParser = require('shell-quote/parse')
4
+
5
+ const ALLOWED_ENV_VARIABLES = ['LD_PRELOAD', 'LD_LIBRARY_PATH', 'PATH']
6
+ const PROCESS_DENYLIST = ['md5']
7
+
8
+ const VARNAMES_REGEX = /\$([\w\d_]*)(?:[^\w\d_]|$)/gmi
9
+ // eslint-disable-next-line max-len
10
+ const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|api_?key|secret|a(?:ccess|uth)_token|mysql_pwd|credentials|(?:stripe)?token)$'
11
+ const regexParam = new RegExp(PARAM_PATTERN, 'i')
12
+ const ENV_PATTERN = '^(\\w+=\\w+;)*\\w+=\\w+;?$'
13
+ const envvarRegex = new RegExp(ENV_PATTERN)
14
+ const REDACTED = '?'
15
+
16
+ function extractVarNames (expression) {
17
+ const varNames = new Set()
18
+ let match
19
+
20
+ while ((match = VARNAMES_REGEX.exec(expression))) {
21
+ varNames.add(match[1])
22
+ }
23
+
24
+ const varNamesObject = {}
25
+ for (const varName of varNames.keys()) {
26
+ varNamesObject[varName] = `$${varName}`
27
+ }
28
+ return varNamesObject
29
+ }
30
+
31
+ function getTokensByExpression (expressionTokens) {
32
+ const expressionListTokens = []
33
+ let wipExpressionTokens = []
34
+ let isNewExpression = true
35
+
36
+ expressionTokens.forEach(token => {
37
+ if (isNewExpression) {
38
+ expressionListTokens.push(wipExpressionTokens)
39
+ isNewExpression = false
40
+ }
41
+
42
+ wipExpressionTokens.push(token)
43
+
44
+ if (token.op) {
45
+ wipExpressionTokens = []
46
+ isNewExpression = true
47
+ }
48
+ })
49
+ return expressionListTokens
50
+ }
51
+
52
+ function scrubChildProcessCmd (expression) {
53
+ const varNames = extractVarNames(expression)
54
+ const expressionTokens = shellParser(expression, varNames)
55
+
56
+ const expressionListTokens = getTokensByExpression(expressionTokens)
57
+
58
+ const result = []
59
+ expressionListTokens.forEach((expressionTokens) => {
60
+ let foundBinary = false
61
+ for (let index = 0; index < expressionTokens.length; index++) {
62
+ const token = expressionTokens[index]
63
+
64
+ if (typeof token === 'object') {
65
+ if (token.pattern) {
66
+ result.push(token.pattern)
67
+ } else if (token.op) {
68
+ result.push(token.op)
69
+ } else if (token.comment) {
70
+ result.push(`#${token.comment}`)
71
+ }
72
+ } else if (!foundBinary) {
73
+ if (envvarRegex.test(token)) {
74
+ const envSplit = token.split('=')
75
+
76
+ if (!ALLOWED_ENV_VARIABLES.includes(envSplit[0])) {
77
+ envSplit[1] = REDACTED
78
+
79
+ const newToken = envSplit.join('=')
80
+ expressionTokens[index] = newToken
81
+
82
+ result.push(newToken)
83
+ } else {
84
+ result.push(token)
85
+ }
86
+ } else {
87
+ foundBinary = true
88
+ result.push(token)
89
+
90
+ if (PROCESS_DENYLIST.includes(token)) {
91
+ for (index++; index < expressionTokens.length; index++) {
92
+ const token = expressionTokens[index]
93
+
94
+ if (token.op) {
95
+ result.push(token.op)
96
+ } else {
97
+ expressionTokens[index] = REDACTED
98
+ result.push(REDACTED)
99
+ }
100
+ }
101
+ break
102
+ }
103
+ }
104
+ } else {
105
+ const paramKeyValue = token.split('=')
106
+ const paramKey = paramKeyValue[0]
107
+
108
+ if (regexParam.test(paramKey)) {
109
+ if (paramKeyValue.length === 1) {
110
+ expressionTokens[index + 1] = REDACTED
111
+ result.push(token)
112
+ } else {
113
+ result.push(`${paramKey}=${REDACTED}`)
114
+ }
115
+ } else {
116
+ result.push(token)
117
+ }
118
+ }
119
+ }
120
+ })
121
+
122
+ return result
123
+ }
124
+
125
+ module.exports = scrubChildProcessCmd
@@ -14,7 +14,8 @@ const {
14
14
  TEST_ITR_UNSKIPPABLE,
15
15
  TEST_ITR_FORCED_RUN,
16
16
  TEST_CODE_OWNERS,
17
- ITR_CORRELATION_ID
17
+ ITR_CORRELATION_ID,
18
+ TEST_SOURCE_FILE
18
19
  } = require('../../dd-trace/src/plugins/util/test')
19
20
  const { RESOURCE_NAME } = require('../../../ext/tags')
20
21
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -47,7 +48,7 @@ class CucumberPlugin extends CiPlugin {
47
48
  hasUnskippableSuites,
48
49
  hasForcedToRunSuites
49
50
  }) => {
50
- const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
51
+ const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
51
52
  addIntelligentTestRunnerSpanTags(
52
53
  this.testSessionSpan,
53
54
  this.testModuleSpan,
@@ -71,7 +72,7 @@ class CucumberPlugin extends CiPlugin {
71
72
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
72
73
  finishAllTraceSpans(this.testSessionSpan)
73
74
 
74
- this.itrConfig = null
75
+ this.libraryConfig = null
75
76
  this.tracer._exporter.flush()
76
77
  })
77
78
 
@@ -102,7 +103,7 @@ class CucumberPlugin extends CiPlugin {
102
103
  }
103
104
  })
104
105
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
105
- if (this.itrConfig?.isCodeCoverageEnabled) {
106
+ if (this.libraryConfig?.isCodeCoverageEnabled) {
106
107
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
107
108
  }
108
109
  })
@@ -114,7 +115,7 @@ class CucumberPlugin extends CiPlugin {
114
115
  })
115
116
 
116
117
  this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
117
- if (!this.itrConfig?.isCodeCoverageEnabled) {
118
+ if (!this.libraryConfig?.isCodeCoverageEnabled) {
118
119
  return
119
120
  }
120
121
  if (!coverageFiles.length) {
@@ -122,7 +123,7 @@ class CucumberPlugin extends CiPlugin {
122
123
  }
123
124
 
124
125
  const relativeCoverageFiles = [...coverageFiles, suiteFile]
125
- .map(filename => getTestSuitePath(filename, this.sourceRoot))
126
+ .map(filename => getTestSuitePath(filename, this.repositoryRoot))
126
127
 
127
128
  this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
128
129
 
@@ -136,10 +137,11 @@ class CucumberPlugin extends CiPlugin {
136
137
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
137
138
  })
138
139
 
139
- this.addSub('ci:cucumber:test:start', ({ testName, fullTestSuite, testSourceLine }) => {
140
+ this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine }) => {
140
141
  const store = storage.getStore()
141
- const testSuite = getTestSuitePath(fullTestSuite, this.sourceRoot)
142
- const testSpan = this.startTestSpan(testName, testSuite, testSourceLine)
142
+ const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
143
+ const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
144
+ const testSpan = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine)
143
145
 
144
146
  this.enter(testSpan, store)
145
147
  })
@@ -191,12 +193,15 @@ class CucumberPlugin extends CiPlugin {
191
193
  })
192
194
  }
193
195
 
194
- startTestSpan (testName, testSuite, testSourceLine) {
196
+ startTestSpan (testName, testSuite, testSourceFile, testSourceLine) {
195
197
  return super.startTestSpan(
196
198
  testName,
197
199
  testSuite,
198
200
  this.testSuiteSpan,
199
- { [TEST_SOURCE_START]: testSourceLine }
201
+ {
202
+ [TEST_SOURCE_START]: testSourceLine,
203
+ [TEST_SOURCE_FILE]: testSourceFile
204
+ }
200
205
  )
201
206
  }
202
207
  }