dd-trace 3.46.0 → 3.48.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 (80) hide show
  1. package/LICENSE-3rdparty.csv +1 -3
  2. package/README.md +1 -32
  3. package/ci/init.js +1 -4
  4. package/index.d.ts +21 -0
  5. package/package.json +6 -8
  6. package/packages/datadog-instrumentations/src/amqplib.js +2 -2
  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 +3 -2
  12. package/packages/datadog-instrumentations/src/jest.js +147 -10
  13. package/packages/datadog-instrumentations/src/mocha.js +3 -3
  14. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  15. package/packages/datadog-instrumentations/src/mquery.js +65 -0
  16. package/packages/datadog-instrumentations/src/next.js +17 -3
  17. package/packages/datadog-instrumentations/src/playwright.js +41 -9
  18. package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
  19. package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
  20. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +134 -1
  21. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +19 -1
  22. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -10
  23. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  24. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  25. package/packages/datadog-plugin-cucumber/src/index.js +16 -11
  26. package/packages/datadog-plugin-cypress/src/plugin.js +25 -12
  27. package/packages/datadog-plugin-graphql/src/index.js +1 -6
  28. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  29. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  30. package/packages/datadog-plugin-http/src/client.js +1 -1
  31. package/packages/datadog-plugin-jest/src/index.js +47 -6
  32. package/packages/datadog-plugin-mocha/src/index.js +14 -5
  33. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  34. package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
  35. package/packages/datadog-plugin-rhea/src/producer.js +11 -0
  36. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  37. package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
  38. package/packages/dd-trace/src/appsec/channels.js +2 -1
  39. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  40. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +22 -17
  41. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  42. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  43. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -1
  44. package/packages/dd-trace/src/appsec/index.js +17 -2
  45. package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
  46. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
  47. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  48. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  49. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
  50. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
  51. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
  52. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
  53. package/packages/dd-trace/src/config.js +22 -9
  54. package/packages/dd-trace/src/datastreams/processor.js +36 -5
  55. package/packages/dd-trace/src/datastreams/writer.js +11 -5
  56. package/packages/dd-trace/src/dogstatsd.js +3 -5
  57. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
  58. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  59. package/packages/dd-trace/src/format.js +25 -1
  60. package/packages/dd-trace/src/noop/span.js +1 -0
  61. package/packages/dd-trace/src/opentelemetry/span.js +9 -2
  62. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -1
  63. package/packages/dd-trace/src/opentracing/span.js +38 -0
  64. package/packages/dd-trace/src/opentracing/span_context.js +12 -6
  65. package/packages/dd-trace/src/opentracing/tracer.js +2 -1
  66. package/packages/dd-trace/src/plugins/ci_plugin.js +24 -8
  67. package/packages/dd-trace/src/plugins/index.js +1 -0
  68. package/packages/dd-trace/src/plugins/util/git.js +6 -0
  69. package/packages/dd-trace/src/plugins/util/test.js +36 -7
  70. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  71. package/packages/dd-trace/src/profiling/config.js +22 -22
  72. package/packages/dd-trace/src/proxy.js +31 -23
  73. package/packages/dd-trace/src/span_processor.js +5 -1
  74. package/packages/dd-trace/src/telemetry/index.js +3 -0
  75. package/packages/dd-trace/src/tracer.js +1 -0
  76. package/packages/utils/src/kebabcase.js +16 -0
  77. package/packages/utils/src/pick.js +11 -0
  78. package/packages/utils/src/uniq.js +5 -0
  79. package/packages/datadog-instrumentations/src/child-process.js +0 -29
  80. package/packages/dd-trace/src/plugins/util/exec.js +0 -34
@@ -1,10 +1,69 @@
1
1
  'use strict'
2
+ const {
3
+ CONTEXT_PROPAGATION_KEY,
4
+ getSizeOrZero
5
+ } = require('../../../dd-trace/src/datastreams/processor')
6
+ const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway')
2
7
  const log = require('../../../dd-trace/src/log')
3
8
  const BaseAwsSdkPlugin = require('../base')
9
+ const { storage } = require('../../../datadog-core')
10
+
4
11
  class Kinesis extends BaseAwsSdkPlugin {
5
12
  static get id () { return 'kinesis' }
6
13
  static get peerServicePrecursors () { return ['streamname'] }
7
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
+
8
67
  generateTags (params, operation, response) {
9
68
  if (!params || !params.StreamName) return {}
10
69
 
@@ -15,6 +74,58 @@ class Kinesis extends BaseAwsSdkPlugin {
15
74
  }
16
75
  }
17
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
+
18
129
  // AWS-SDK will b64 kinesis payloads
19
130
  // or will accept an already b64 encoded payload
20
131
  // This method handles both
@@ -37,8 +148,9 @@ class Kinesis extends BaseAwsSdkPlugin {
37
148
  if (!request.params) {
38
149
  return
39
150
  }
40
-
41
151
  const traceData = {}
152
+
153
+ // inject data with DD context
42
154
  this.tracer.inject(span, 'text_map', traceData)
43
155
  let injectPath
44
156
  if (request.params.Records && request.params.Records.length > 0) {
@@ -49,9 +161,30 @@ class Kinesis extends BaseAwsSdkPlugin {
49
161
  log.error('No valid payload passed, unable to pass trace context')
50
162
  return
51
163
  }
164
+
52
165
  const parsedData = this._tryParse(injectPath.Data)
53
166
  if (parsedData) {
54
167
  parsedData._datadog = traceData
168
+
169
+ // set DSM hash if enabled
170
+ if (this.config.dsmEnabled) {
171
+ // get payload size of request data
172
+ const payloadSize = Buffer.from(JSON.stringify(parsedData)).byteLength
173
+ let stream
174
+ // users can optionally use either stream name or stream arn
175
+ if (request.params && request.params.StreamArn) {
176
+ stream = request.params.StreamArn
177
+ } else if (request.params && request.params.StreamName) {
178
+ stream = request.params.StreamName
179
+ }
180
+ const dataStreamsContext = this.tracer
181
+ .setCheckpoint(['direction:out', `topic:${stream}`, 'type:kinesis'], span, payloadSize)
182
+ if (dataStreamsContext) {
183
+ const pathwayCtx = encodePathwayContext(dataStreamsContext)
184
+ parsedData._datadog[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON()
185
+ }
186
+ }
187
+
55
188
  const finalData = Buffer.from(JSON.stringify(parsedData))
56
189
  const byteSize = finalData.length
57
190
  // Kinesis max payload size is 1MB
@@ -1,4 +1,6 @@
1
1
  'use strict'
2
+ const { CONTEXT_PROPAGATION_KEY, getHeadersSize } = require('../../../dd-trace/src/datastreams/processor')
3
+ const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway')
2
4
  const log = require('../../../dd-trace/src/log')
3
5
  const BaseAwsSdkPlugin = require('../base')
4
6
 
@@ -11,6 +13,7 @@ class Sns extends BaseAwsSdkPlugin {
11
13
 
12
14
  if (!params.TopicArn && !(response.data && response.data.TopicArn)) return {}
13
15
  const TopicArn = params.TopicArn || response.data.TopicArn
16
+
14
17
  // Split the ARN into its parts
15
18
  // ex.'arn:aws:sns:us-east-1:123456789012:my-topic'
16
19
  const arnParts = TopicArn.split(':')
@@ -72,10 +75,25 @@ class Sns extends BaseAwsSdkPlugin {
72
75
  }
73
76
  const ddInfo = {}
74
77
  this.tracer.inject(span, 'text_map', ddInfo)
78
+ // add ddInfo before checking DSM so we can include DD attributes in payload size
75
79
  params.MessageAttributes._datadog = {
76
80
  DataType: 'Binary',
77
- BinaryValue: Buffer.from(JSON.stringify(ddInfo)) // BINARY types are automatically base64 encoded
81
+ BinaryValue: ddInfo
82
+ }
83
+ if (this.config.dsmEnabled) {
84
+ const payloadSize = getHeadersSize({
85
+ Message: params.Message,
86
+ MessageAttributes: params.MessageAttributes
87
+ })
88
+ const dataStreamsContext = this.tracer
89
+ .setCheckpoint(['direction:out', `topic:${params.TopicArn}`, 'type:sns'], span, payloadSize)
90
+ if (dataStreamsContext) {
91
+ const pathwayCtx = encodePathwayContext(dataStreamsContext)
92
+ ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON()
93
+ }
78
94
  }
95
+ // BINARY types are automatically base64 encoded
96
+ params.MessageAttributes._datadog.BinaryValue = Buffer.from(JSON.stringify(ddInfo))
79
97
  }
80
98
  }
81
99
 
@@ -3,6 +3,8 @@
3
3
  const log = require('../../../dd-trace/src/log')
4
4
  const BaseAwsSdkPlugin = require('../base')
5
5
  const { storage } = require('../../../datadog-core')
6
+ const { CONTEXT_PROPAGATION_KEY, getHeadersSize } = require('../../../dd-trace/src/datastreams/processor')
7
+ const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway')
6
8
 
7
9
  class Sqs extends BaseAwsSdkPlugin {
8
10
  static get id () { return 'sqs' }
@@ -19,20 +21,27 @@ class Sqs extends BaseAwsSdkPlugin {
19
21
  const { request, response } = obj
20
22
  const store = storage.getStore()
21
23
  const plugin = this
22
- const maybeChildOf = this.responseExtract(request.params, request.operation, response)
23
- if (maybeChildOf) {
24
+ const contextExtraction = this.responseExtract(request.params, request.operation, response)
25
+ let span
26
+ let parsedMessageAttributes
27
+ if (contextExtraction && contextExtraction.datadogContext) {
24
28
  obj.needsFinish = true
25
29
  const options = {
26
- childOf: maybeChildOf,
30
+ childOf: contextExtraction.datadogContext,
27
31
  tags: Object.assign(
28
32
  {},
29
33
  this.requestTags.get(request) || {},
30
34
  { 'span.kind': 'server' }
31
35
  )
32
36
  }
33
- const span = plugin.tracer.startSpan('aws.response', options)
37
+ parsedMessageAttributes = contextExtraction.parsedAttributes
38
+ span = plugin.tracer.startSpan('aws.response', options)
34
39
  this.enter(span, store)
35
40
  }
41
+ // extract DSM context after as we might not have a parent-child but may have a DSM context
42
+ this.responseExtractDSMContext(
43
+ request.operation, request.params, response, span ?? null, parsedMessageAttributes ?? null
44
+ )
36
45
  })
37
46
 
38
47
  this.addSub('apm:aws:response:finish:sqs', err => {
@@ -133,19 +142,69 @@ class Sqs extends BaseAwsSdkPlugin {
133
142
 
134
143
  const datadogAttribute = message.MessageAttributes._datadog
135
144
 
145
+ const parsedAttributes = this.parseDatadogAttributes(datadogAttribute)
146
+ if (parsedAttributes) {
147
+ return {
148
+ datadogContext: this.tracer.extract('text_map', parsedAttributes),
149
+ parsedAttributes: parsedAttributes
150
+ }
151
+ }
152
+ }
153
+
154
+ parseDatadogAttributes (attributes) {
136
155
  try {
137
- if (datadogAttribute.StringValue) {
138
- const textMap = datadogAttribute.StringValue
139
- return this.tracer.extract('text_map', JSON.parse(textMap))
140
- } else if (datadogAttribute.Type === 'Binary') {
141
- const buffer = Buffer.from(datadogAttribute.Value, 'base64')
142
- return this.tracer.extract('text_map', JSON.parse(buffer))
156
+ if (attributes.StringValue) {
157
+ const textMap = attributes.StringValue
158
+ return JSON.parse(textMap)
159
+ } else if (attributes.Type === 'Binary') {
160
+ const buffer = Buffer.from(attributes.Value, 'base64')
161
+ return JSON.parse(buffer)
143
162
  }
144
163
  } catch (e) {
145
164
  log.error(e)
146
165
  }
147
166
  }
148
167
 
168
+ responseExtractDSMContext (operation, params, response, span, parsedAttributes) {
169
+ if (!this.config.dsmEnabled) return
170
+ if (operation !== 'receiveMessage') return
171
+ if (!response || !response.Messages || !response.Messages[0]) return
172
+
173
+ // we only want to set the payloadSize on the span if we have one message
174
+ span = response.Messages.length > 1 ? null : span
175
+
176
+ response.Messages.forEach(message => {
177
+ // we may have already parsed the message attributes when extracting trace context
178
+ if (!parsedAttributes) {
179
+ if (message.Body) {
180
+ try {
181
+ const body = JSON.parse(message.Body)
182
+
183
+ // SNS to SQS
184
+ if (body.Type === 'Notification') {
185
+ message = body
186
+ }
187
+ } catch (e) {
188
+ // SQS to SQS
189
+ }
190
+ }
191
+ if (message.MessageAttributes && message.MessageAttributes._datadog) {
192
+ parsedAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog)
193
+ }
194
+ }
195
+ if (parsedAttributes && parsedAttributes[CONTEXT_PROPAGATION_KEY]) {
196
+ const payloadSize = getHeadersSize({
197
+ Body: message.Body,
198
+ MessageAttributes: message.MessageAttributes
199
+ })
200
+ const queue = params.QueueUrl.split('/').pop()
201
+ this.tracer.decodeDataStreamsContext(Buffer.from(parsedAttributes[CONTEXT_PROPAGATION_KEY]))
202
+ this.tracer
203
+ .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize)
204
+ }
205
+ })
206
+ }
207
+
149
208
  requestInject (span, request) {
150
209
  const operation = request.operation
151
210
  if (operation === 'sendMessage') {
@@ -164,6 +223,20 @@ class Sqs extends BaseAwsSdkPlugin {
164
223
  DataType: 'String',
165
224
  StringValue: JSON.stringify(ddInfo)
166
225
  }
226
+ if (this.config.dsmEnabled) {
227
+ const payloadSize = getHeadersSize({
228
+ Body: request.params.MessageBody,
229
+ MessageAttributes: request.params.MessageAttributes
230
+ })
231
+ const queue = request.params.QueueUrl.split('/').pop()
232
+ const dataStreamsContext = this.tracer
233
+ .setCheckpoint(['direction:out', `topic:${queue}`, 'type:sqs'], span, payloadSize)
234
+ if (dataStreamsContext) {
235
+ const pathwayCtx = encodePathwayContext(dataStreamsContext)
236
+ ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON()
237
+ }
238
+ }
239
+ request.params.MessageAttributes._datadog.StringValue = JSON.stringify(ddInfo)
167
240
  }
168
241
  }
169
242
  }
@@ -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
  }