dd-trace 6.14.0 → 6.15.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 (22) hide show
  1. package/index.d.ts +8 -0
  2. package/package.json +3 -3
  3. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
  4. package/packages/datadog-instrumentations/src/jest.js +141 -25
  5. package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
  6. package/packages/datadog-instrumentations/src/playwright.js +4 -0
  7. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  8. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
  9. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  10. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
  11. package/packages/dd-trace/src/config/supported-configurations.json +2 -0
  12. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
  13. package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
  14. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
  15. package/packages/dd-trace/src/exporters/common/proxy.js +52 -0
  16. package/packages/dd-trace/src/exporters/common/request.js +11 -3
  17. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +5 -0
  18. package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
  19. package/packages/dd-trace/src/priority_sampler.js +4 -0
  20. package/packages/dd-trace/src/sampling_rule.js +3 -1
  21. package/packages/dd-trace/src/span_processor.js +18 -3
  22. package/packages/dd-trace/src/telemetry/send-data.js +5 -4
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { getHttpsProxyAgent } = require('../../evp_proxy/direct')
4
-
5
3
  /**
6
4
  * @param {ReturnType<import('../config')>} config - Debugger configuration
7
5
  * @param {string} path - Request path
@@ -10,8 +8,7 @@ const { getHttpsProxyAgent } = require('../../evp_proxy/direct')
10
8
  * method: 'POST',
11
9
  * url: string | URL,
12
10
  * path: string,
13
- * headers: Record<string, string>,
14
- * agent?: import('node:https').Agent
11
+ * headers: Record<string, string>
15
12
  * }}
16
13
  */
17
14
  module.exports = function getRequestOptions (config, path, headers) {
@@ -25,8 +22,6 @@ module.exports = function getRequestOptions (config, path, headers) {
25
22
  if (config.agentless) {
26
23
  if (config.apiKey !== undefined) headers['DD-API-KEY'] = config.apiKey
27
24
  headers['DD-EVP-ORIGIN'] = 'agent-debugger'
28
- const agent = getHttpsProxyAgent(config.url.href)
29
- if (agent !== undefined) options.agent = agent
30
25
  }
31
26
 
32
27
  return options
@@ -1,19 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { HttpsProxyAgent } = require('../../../../vendor/dist/https-proxy-agent')
4
- const { getProxyForUrl } = require('../../../../vendor/dist/proxy-from-env')
5
3
  const { createSiteUrl } = require('../exporters/common/url')
6
4
  const log = require('../log')
7
5
 
8
- /** @type {Map<string, import('node:https').Agent>} */
9
- const proxyAgents = new Map()
10
-
11
6
  /**
12
7
  * @typedef {object} DirectEVPRoute
13
8
  * @property {URL} url - Direct intake URL
14
9
  * @property {string} basePath - Direct intake base path
15
10
  * @property {object} headers - Direct intake authentication headers
16
- * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
17
11
  */
18
12
 
19
13
  /**
@@ -33,36 +27,16 @@ function createDirectEVPRoute (config, intake) {
33
27
  const url = createSiteUrl(config.site, intake)
34
28
  if (url === undefined) throw new Error('Invalid direct EVP intake URL')
35
29
 
36
- const agent = getHttpsProxyAgent(url.href)
37
-
38
- const route = {
30
+ return {
39
31
  url,
40
32
  basePath: '',
41
33
  headers: {
42
34
  'DD-API-KEY': apiKey,
43
35
  },
44
36
  }
45
- if (agent) route.agent = agent
46
- return route
47
37
  } catch (error) {
48
38
  log.debug('Unable to configure direct EVP intake: %s', error.message)
49
39
  }
50
40
  }
51
41
 
52
- /**
53
- * @param {string} url
54
- * @returns {import('node:https').Agent|undefined}
55
- */
56
- function getHttpsProxyAgent (url) {
57
- const proxyUrl = getProxyForUrl(url)
58
- if (!proxyUrl) return
59
-
60
- let agent = proxyAgents.get(proxyUrl)
61
- if (agent === undefined) {
62
- agent = new HttpsProxyAgent(proxyUrl)
63
- proxyAgents.set(proxyUrl, agent)
64
- }
65
- return agent
66
- }
67
-
68
- module.exports = { createDirectEVPRoute, getHttpsProxyAgent }
42
+ module.exports = { createDirectEVPRoute }
@@ -8,6 +8,7 @@ const log = require('../../log')
8
8
  const tracerVersion = require('../../../../../package.json').version
9
9
 
10
10
  const { canSendApiKey } = require('../common/url')
11
+ const { getHttpsProxyAgent } = require('../common/proxy')
11
12
  const BaseWriter = require('../common/writer')
12
13
  const { AgentEncoder } = require('../../encode/0.4')
13
14
  const { computeIntakeUrl, INTAKE_PATH } = require('./intake')
@@ -153,6 +154,7 @@ class AgentlessWriter extends BaseWriter {
153
154
 
154
155
  this.#closeExporter()
155
156
  const config = getConfig()
157
+ const agent = this._url.protocol === 'https:' ? getHttpsProxyAgent(this._url) : undefined
156
158
  this.#exporter = createAgentlessExporter({
157
159
  endpoint: this.#endpoint(),
158
160
  apiKey,
@@ -165,7 +167,7 @@ class AgentlessWriter extends BaseWriter {
165
167
  tracerVersion,
166
168
  languageVersion: process.version,
167
169
  languageInterpreter: process.versions.bun ? 'JavaScriptCore' : 'v8',
168
- })
170
+ }, { agent })
169
171
  this.#exporterApiKey = apiKey
170
172
  this.#exporterEnv = env
171
173
  this.#exporterRuntimeId = runtimeID
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ const { isIPv6 } = require('node:net')
4
+
5
+ let defaultAgentKey
6
+ let getProxyForUrl
7
+ let proxyAgents
8
+
9
+ /**
10
+ * Selects a proxy agent for an HTTPS target while preserving the direct agent's pool boundaries.
11
+ *
12
+ * @param {string|URL|object} url
13
+ * @param {import('node:http').Agent|false} [directAgent]
14
+ * @returns {import('node:http').Agent|false|undefined}
15
+ */
16
+ function getHttpsProxyAgent (url, directAgent) {
17
+ getProxyForUrl ??= require('../../../../../vendor/dist/proxy-from-env').getProxyForUrl
18
+
19
+ const host = typeof url === 'string' ? undefined : url.host ?? url.hostname
20
+ const isUnbracketedIPv6 = typeof host === 'string' && host.includes(':') && isIPv6(host)
21
+ const target = typeof url === 'string'
22
+ ? url
23
+ : { protocol: url.protocol, host: isUnbracketedIPv6 ? `[${host}]` : host, port: url.port }
24
+ const proxyUrl = getProxyForUrl(target)
25
+ if (!proxyUrl) return directAgent
26
+
27
+ directAgent ??= require('node:https').globalAgent
28
+
29
+ if (proxyAgents === undefined) {
30
+ defaultAgentKey = {}
31
+ proxyAgents = new WeakMap()
32
+ }
33
+ const cacheKey = directAgent || defaultAgentKey
34
+ let agents = proxyAgents.get(cacheKey)
35
+ if (agents === undefined) {
36
+ agents = new Map()
37
+ proxyAgents.set(cacheKey, agents)
38
+ }
39
+
40
+ let agent = agents.get(proxyUrl)
41
+ if (agent === undefined) {
42
+ const { HttpsProxyAgent } = require('../../../../../vendor/dist/https-proxy-agent')
43
+ const options = directAgent
44
+ ? { keepAlive: directAgent.keepAlive, maxSockets: directAgent.maxSockets }
45
+ : undefined
46
+ agent = new HttpsProxyAgent(proxyUrl, options)
47
+ agents.set(proxyUrl, agent)
48
+ }
49
+ return agent
50
+ }
51
+
52
+ module.exports = { getHttpsProxyAgent }
@@ -13,6 +13,7 @@ const log = require('../../log')
13
13
  const { canSendApiKey, parseUrl } = require('./url')
14
14
  const docker = require('./docker')
15
15
  const { httpAgent, httpsAgent } = require('./agents')
16
+ const { getHttpsProxyAgent } = require('./proxy')
16
17
  const {
17
18
  getMaxAttempts,
18
19
  getRetryDelay,
@@ -94,11 +95,18 @@ function request (data, options, callback) {
94
95
 
95
96
  docker.inject(options.headers)
96
97
 
97
- const connectionOptions = {
98
- ...options,
99
- agent: options.agent ?? (isSecure ? httpsAgent : httpAgent),
98
+ let agent = options.agent ?? (isSecure ? httpsAgent : httpAgent)
99
+ if (hasApiKey && isSecure) {
100
+ try {
101
+ agent = getHttpsProxyAgent(options, agent)
102
+ } catch (error) {
103
+ callback(error)
104
+ return
105
+ }
100
106
  }
101
107
 
108
+ const connectionOptions = { ...options, agent }
109
+
102
110
  /**
103
111
  * @param {import('node:http').IncomingMessage} res
104
112
  * @param {(error: Error|null, result?: string|null, statusCode?: number,
@@ -6,7 +6,9 @@ const { URL } = require('node:url')
6
6
  const { storage } = require('../../../../datadog-core')
7
7
  const log = require('../../log')
8
8
  const { createServerlessDeliveryTracker } = require('../../serverless')
9
+ const { getHttpsProxyAgent } = require('../../exporters/common/proxy')
9
10
  const telemetryMetrics = require('../../telemetry/metrics')
11
+ const { version: tracerVersion } = require('../../../../../package.json')
10
12
 
11
13
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
12
14
  const legacyStorage = storage('legacy')
@@ -48,8 +50,10 @@ class OtlpHttpExporterBase {
48
50
  hostname: parsedUrl.hostname,
49
51
  port: parsedUrl.port,
50
52
  path: parsedUrl.pathname + parsedUrl.search,
53
+ agent: parsedUrl.protocol === 'https:' ? getHttpsProxyAgent(parsedUrl) : undefined,
51
54
  headers: {
52
55
  'Content-Type': isJson ? 'application/json' : 'application/x-protobuf',
56
+ 'User-Agent': `dd-trace-js/${tracerVersion}`,
53
57
  ...headers,
54
58
  },
55
59
  }
@@ -169,6 +173,7 @@ class OtlpHttpExporterBase {
169
173
  this.options.hostname = parsedUrl.hostname
170
174
  this.options.port = parsedUrl.port
171
175
  this.options.path = parsedUrl.pathname + parsedUrl.search
176
+ this.options.agent = parsedUrl.protocol === 'https:' ? getHttpsProxyAgent(parsedUrl) : undefined
172
177
  this.telemetryTags[0] = `protocol:${this.#transport === https ? 'https' : 'http'}`
173
178
  }
174
179
 
@@ -363,6 +363,11 @@ class TextMapPropagator {
363
363
  * @returns {DatadogSpanContext | null}
364
364
  */
365
365
  extract (carrier) {
366
+ if (!carrier || typeof carrier !== 'object') {
367
+ if (this.#config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT !== 'ignore') removeAllBaggageItems()
368
+ return null
369
+ }
370
+
366
371
  const spanContext = this.#extractSpanContext(carrier)
367
372
  if (spanContext === undefined) return null
368
373
 
@@ -825,7 +830,6 @@ class TextMapPropagator {
825
830
  * @returns {DatadogSpanContext | undefined}
826
831
  */
827
832
  #extractDatadogContext (carrier) {
828
- if (!carrier) return
829
833
  const traceId = readDatadogTraceId(carrier)
830
834
  if (!traceId) return
831
835
  const spanContext = extractGenericContext(traceId, readDatadogParentId(carrier), 10)
@@ -860,6 +864,8 @@ class TextMapPropagator {
860
864
  } catch {
861
865
  return
862
866
  }
867
+ if (!parsed || typeof parsed !== 'object') return
868
+
863
869
  const spanContext = this.#extractDatadogContext(parsed)
864
870
  if (!spanContext) return
865
871
 
@@ -983,7 +989,7 @@ class TextMapPropagator {
983
989
  */
984
990
  #extractBaggageItems (carrier, spanContext, extractBaggage) {
985
991
  removeAllBaggageItems()
986
- if (!carrier || !extractBaggage) return
992
+ if (!extractBaggage) return
987
993
  const header = readBaggage(carrier)
988
994
  if (!header) return
989
995
 
@@ -263,6 +263,10 @@ class PrioritySampler {
263
263
  context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
264
264
  }
265
265
 
266
+ if (rule.discard) {
267
+ context._sampling.discard = true
268
+ }
269
+
266
270
  return rule.sample(context) && this._isSampledByRateLimit(context)
267
271
  ? USER_KEEP
268
272
  : USER_REJECT
@@ -185,6 +185,7 @@ function resourceLocator (span) {
185
185
  * @property {number} [sampleRate=1] - Deterministic sampling rate in [0, 1].
186
186
  * @property {string} [provenance] - Optional provenance/metadata for this rule.
187
187
  * @property {number} [maxPerSecond] - Maximum samples per second (rate limit).
188
+ * @property {boolean} [discard=false] - Whether to fully drop a trace if not kept.
188
189
  */
189
190
 
190
191
  /**
@@ -195,7 +196,7 @@ class SamplingRule {
195
196
  /**
196
197
  * @param {SamplingRuleConfig} [config]
197
198
  */
198
- constructor ({ name, service, resource, tags, sampleRate = 1, provenance, maxPerSecond } = {}) {
199
+ constructor ({ name, service, resource, tags, sampleRate = 1, provenance, maxPerSecond, discard = false } = {}) {
199
200
  this.matchers = []
200
201
 
201
202
  if (name !== undefined) {
@@ -216,6 +217,7 @@ class SamplingRule {
216
217
  this._sampler = new Sampler(sampleRate)
217
218
  this._limiter = undefined
218
219
  this.provenance = provenance
220
+ this.discard = !!discard
219
221
 
220
222
  if (Number.isFinite(maxPerSecond)) {
221
223
  this._limiter = new RateLimiter(maxPerSecond)
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { AUTO_REJECT } = require('../../../ext/priority')
3
4
  const log = require('./log')
4
5
  const spanFormat = require('./span_format')
5
6
  const SpanSampler = require('./span_sampler')
@@ -34,7 +35,20 @@ class SpanProcessor {
34
35
  sample (span) {
35
36
  const spanContext = span.context()
36
37
  this._prioritySampler.sample(spanContext)
37
- this._spanSampler.sample(spanContext)
38
+ if (!this.#isDiscarded(spanContext)) {
39
+ this._spanSampler.sample(spanContext)
40
+ }
41
+ }
42
+
43
+ /**
44
+ * A rule's reject decision can later be overridden (e.g. a product force-keeping the trace via
45
+ * `PrioritySampler.keepTrace()`), so `discard` only applies while the priority is still a reject.
46
+ *
47
+ * @param {import('./opentracing/span_context')} spanContext
48
+ * @returns {boolean}
49
+ */
50
+ #isDiscarded (spanContext) {
51
+ return spanContext._sampling.discard && spanContext._sampling.priority <= AUTO_REJECT
38
52
  }
39
53
 
40
54
  process (span) {
@@ -56,11 +70,12 @@ class SpanProcessor {
56
70
 
57
71
  let isFirstSpanInChunk = true
58
72
  const stampApmDisabled = this._config.apmTracingEnabled === false
73
+ const discard = this.#isDiscarded(spanContext)
59
74
 
60
75
  for (const span of started) {
61
76
  if (span._duration === undefined) {
62
77
  active.push(span)
63
- } else {
78
+ } else if (!discard) {
64
79
  const formattedSpan = spanFormat(span, isFirstSpanInChunk, this._processTags)
65
80
  if (stampApmDisabled) {
66
81
  formattedSpan.metrics[APM_TRACING_ENABLED_KEY] = 0
@@ -76,7 +91,7 @@ class SpanProcessor {
76
91
  }
77
92
  }
78
93
 
79
- if (formatted.length !== 0 && trace.isRecording !== false) {
94
+ if (!discard && formatted.length !== 0 && trace.isRecording !== false) {
80
95
  this._exporter.export(formatted)
81
96
  }
82
97
 
@@ -76,9 +76,10 @@ let getTestOptimizationAgent
76
76
  * @param {import('../config/config-base')} config
77
77
  * @param {TelemetryApplication} application
78
78
  * @param {TelemetryRequestType} reqType
79
+ * @param {string} [apiKey]
79
80
  * @returns {Record<string, string>}
80
81
  */
81
- function getHeaders (config, application, reqType) {
82
+ function getHeaders (config, application, reqType, apiKey) {
82
83
  const headers = {
83
84
  'DD-Client-Library-Language': application.language_name,
84
85
  'DD-Client-Library-Version': application.tracer_version,
@@ -94,8 +95,8 @@ function getHeaders (config, application, reqType) {
94
95
  if (debug) {
95
96
  headers['dd-telemetry-debug-enabled'] = 'true'
96
97
  }
97
- if (config.DD_API_KEY) {
98
- headers['dd-api-key'] = config.DD_API_KEY
98
+ if (apiKey) {
99
+ headers['dd-api-key'] = apiKey
99
100
  }
100
101
  return headers
101
102
  }
@@ -161,7 +162,7 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
161
162
  port,
162
163
  method: 'POST',
163
164
  path: isAgentlessMode ? '/api/v2/apmtelemetry' : '/telemetry/proxy/api/v2/apmtelemetry',
164
- headers: getHeaders(config, application, reqType),
165
+ headers: getHeaders(config, application, reqType, isAgentlessMode ? config.DD_API_KEY : undefined),
165
166
  }
166
167
  if (isCiVisibility) options.agent = getTestOptimizationAgent(url)
167
168