dd-trace 5.17.0 → 5.18.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 (63) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ext/exporters.d.ts +1 -1
  3. package/index.d.ts +47 -1
  4. package/init.js +40 -1
  5. package/initialize.mjs +8 -5
  6. package/package.json +24 -20
  7. package/packages/datadog-core/src/storage/index.js +1 -10
  8. package/packages/datadog-esbuild/index.js +5 -1
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
  10. package/packages/datadog-instrumentations/src/cucumber.js +76 -34
  11. package/packages/datadog-instrumentations/src/helpers/hook.js +8 -3
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
  14. package/packages/datadog-instrumentations/src/helpers/register.js +56 -5
  15. package/packages/datadog-instrumentations/src/mocha/main.js +12 -1
  16. package/packages/datadog-instrumentations/src/mocha/utils.js +58 -14
  17. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -0
  18. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  19. package/packages/datadog-instrumentations/src/vitest.js +303 -0
  20. package/packages/datadog-plugin-aws-sdk/src/base.js +8 -1
  21. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  22. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +6 -1
  23. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +23 -5
  24. package/packages/datadog-plugin-child_process/src/index.js +1 -1
  25. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  26. package/packages/datadog-plugin-mocha/src/index.js +25 -4
  27. package/packages/datadog-plugin-openai/src/index.js +52 -30
  28. package/packages/datadog-plugin-openai/src/token-estimator.js +20 -0
  29. package/packages/datadog-plugin-vitest/src/index.js +156 -0
  30. package/packages/dd-trace/src/appsec/iast/path-line.js +2 -19
  31. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -0
  32. package/packages/dd-trace/src/appsec/index.js +1 -1
  33. package/packages/dd-trace/src/appsec/rasp.js +32 -5
  34. package/packages/dd-trace/src/appsec/recommended.json +208 -3
  35. package/packages/dd-trace/src/appsec/reporter.js +64 -20
  36. package/packages/dd-trace/src/appsec/sdk/track_event.js +3 -0
  37. package/packages/dd-trace/src/appsec/stack_trace.js +90 -0
  38. package/packages/dd-trace/src/appsec/standalone.js +130 -0
  39. package/packages/dd-trace/src/appsec/telemetry.js +33 -1
  40. package/packages/dd-trace/src/appsec/waf/index.js +2 -2
  41. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +2 -2
  42. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  43. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  44. package/packages/dd-trace/src/config.js +110 -40
  45. package/packages/dd-trace/src/constants.js +3 -1
  46. package/packages/dd-trace/src/datastreams/processor.js +2 -1
  47. package/packages/dd-trace/src/exporters/agent/index.js +2 -2
  48. package/packages/dd-trace/src/format.js +1 -0
  49. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -0
  50. package/packages/dd-trace/src/opentracing/span.js +4 -1
  51. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  52. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -0
  53. package/packages/dd-trace/src/plugins/index.js +2 -0
  54. package/packages/dd-trace/src/plugins/util/test.js +5 -1
  55. package/packages/dd-trace/src/priority_sampler.js +2 -5
  56. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  57. package/packages/dd-trace/src/proxy.js +3 -1
  58. package/packages/dd-trace/src/rate_limiter.js +2 -2
  59. package/packages/dd-trace/src/span_stats.js +4 -3
  60. package/packages/dd-trace/src/telemetry/init-telemetry.js +75 -0
  61. package/packages/dd-trace/src/tracer.js +2 -2
  62. package/packages/dd-trace/src/util.js +6 -1
  63. package/packages/datadog-core/src/storage/async_hooks.js +0 -49
@@ -31,7 +31,10 @@ function newStore () {
31
31
  return {
32
32
  [DD_TELEMETRY_REQUEST_METRICS]: {
33
33
  duration: 0,
34
- durationExt: 0
34
+ durationExt: 0,
35
+ raspDuration: 0,
36
+ raspDurationExt: 0,
37
+ raspEvalCount: 0
35
38
  }
36
39
  }
37
40
  }
@@ -76,6 +79,28 @@ function getOrCreateMetricTags (store, versionsTags) {
76
79
  return metricTags
77
80
  }
78
81
 
82
+ function updateRaspRequestsMetricTags (metrics, req, raspRuleType) {
83
+ if (!req) return
84
+
85
+ const store = getStore(req)
86
+
87
+ // it does not depend on whether telemetry is enabled or not
88
+ addRaspRequestMetrics(store, metrics)
89
+
90
+ if (!enabled) return
91
+
92
+ const tags = { rule_type: raspRuleType, waf_version: metrics.wafVersion }
93
+ appsecMetrics.count('appsec.rasp.rule.eval', tags).inc(1)
94
+
95
+ if (metrics.wafTimeout) {
96
+ appsecMetrics.count('appsec.rasp.timeout', tags).inc(1)
97
+ }
98
+
99
+ if (metrics.ruleTriggered) {
100
+ appsecMetrics.count('appsec.rasp.rule.match', tags).inc(1)
101
+ }
102
+ }
103
+
79
104
  function updateWafRequestsMetricTags (metrics, req) {
80
105
  if (!req) return
81
106
 
@@ -141,6 +166,12 @@ function addRequestMetrics (store, { duration, durationExt }) {
141
166
  store[DD_TELEMETRY_REQUEST_METRICS].durationExt += durationExt || 0
142
167
  }
143
168
 
169
+ function addRaspRequestMetrics (store, { duration, durationExt }) {
170
+ store[DD_TELEMETRY_REQUEST_METRICS].raspDuration += duration || 0
171
+ store[DD_TELEMETRY_REQUEST_METRICS].raspDurationExt += durationExt || 0
172
+ store[DD_TELEMETRY_REQUEST_METRICS].raspEvalCount++
173
+ }
174
+
144
175
  function getRequestMetrics (req) {
145
176
  if (req) {
146
177
  const store = getStore(req)
@@ -153,6 +184,7 @@ module.exports = {
153
184
  disable,
154
185
 
155
186
  updateWafRequestsMetricTags,
187
+ updateRaspRequestsMetricTags,
156
188
  incrementWafInitMetric,
157
189
  incrementWafUpdatesMetric,
158
190
  incrementWafRequestsMetric,
@@ -46,7 +46,7 @@ function update (newRules) {
46
46
  }
47
47
  }
48
48
 
49
- function run (data, req) {
49
+ function run (data, req, raspRuleType) {
50
50
  if (!req) {
51
51
  const store = storage.getStore()
52
52
  if (!store || !store.req) {
@@ -59,7 +59,7 @@ function run (data, req) {
59
59
 
60
60
  const wafContext = waf.wafManager.getWAFContext(req)
61
61
 
62
- return wafContext.run(data)
62
+ return wafContext.run(data, raspRuleType)
63
63
  }
64
64
 
65
65
  function disposeContext (req) {
@@ -19,7 +19,7 @@ class WAFContextWrapper {
19
19
  this.addressesToSkip = new Set()
20
20
  }
21
21
 
22
- run ({ persistent, ephemeral }) {
22
+ run ({ persistent, ephemeral }, raspRuleType) {
23
23
  const payload = {}
24
24
  let payloadHasData = false
25
25
  const inputs = {}
@@ -72,7 +72,7 @@ class WAFContextWrapper {
72
72
  blockTriggered,
73
73
  wafVersion: this.wafVersion,
74
74
  wafTimeout: result.timeout
75
- })
75
+ }, raspRuleType)
76
76
 
77
77
  if (ruleTriggered) {
78
78
  Reporter.reportAttack(JSON.stringify(result.events))
@@ -190,7 +190,8 @@ class CiVisibilityExporter extends AgentInfoExporter {
190
190
  requireGit,
191
191
  isEarlyFlakeDetectionEnabled,
192
192
  earlyFlakeDetectionNumRetries,
193
- earlyFlakeDetectionFaultyThreshold
193
+ earlyFlakeDetectionFaultyThreshold,
194
+ isFlakyTestRetriesEnabled
194
195
  } = remoteConfiguration
195
196
  return {
196
197
  isCodeCoverageEnabled,
@@ -199,7 +200,8 @@ class CiVisibilityExporter extends AgentInfoExporter {
199
200
  requireGit,
200
201
  isEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionEnabled && this._config.isEarlyFlakeDetectionEnabled,
201
202
  earlyFlakeDetectionNumRetries,
202
- earlyFlakeDetectionFaultyThreshold
203
+ earlyFlakeDetectionFaultyThreshold,
204
+ isFlakyTestRetriesEnabled
203
205
  }
204
206
  }
205
207
 
@@ -93,7 +93,8 @@ function getLibraryConfiguration ({
93
93
  tests_skipping: isSuitesSkippingEnabled,
94
94
  itr_enabled: isItrEnabled,
95
95
  require_git: requireGit,
96
- early_flake_detection: earlyFlakeDetectionConfig
96
+ early_flake_detection: earlyFlakeDetectionConfig,
97
+ flaky_test_retries_enabled: isFlakyTestRetriesEnabled
97
98
  }
98
99
  }
99
100
  } = JSON.parse(res)
@@ -107,7 +108,8 @@ function getLibraryConfiguration ({
107
108
  earlyFlakeDetectionNumRetries:
108
109
  earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] || DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
109
110
  earlyFlakeDetectionFaultyThreshold:
110
- earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD
111
+ earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
112
+ isFlakyTestRetriesEnabled
111
113
  }
112
114
 
113
115
  log.debug(() => `Remote settings: ${JSON.stringify(settings)}`)
@@ -26,50 +26,104 @@ const telemetryCounters = {
26
26
  'otel.env.invalid': {}
27
27
  }
28
28
 
29
- function getCounter (event, ddVar, otelVar, otelTracesSamplerArg) {
29
+ function getCounter (event, ddVar, otelVar) {
30
30
  const counters = telemetryCounters[event]
31
31
  const tags = []
32
+ const ddVarPrefix = 'config.datadog:'
33
+ const otelVarPrefix = 'config.opentelemetry:'
34
+ if (ddVar) {
35
+ ddVar = ddVarPrefix + ddVar
36
+ tags.push(ddVar)
37
+ }
38
+ if (otelVar) {
39
+ otelVar = otelVarPrefix + otelVar
40
+ tags.push(otelVar)
41
+ }
32
42
 
33
- if (ddVar) tags.push(ddVar)
34
- if (otelVar) tags.push(otelVar)
35
- if (otelTracesSamplerArg) tags.push(otelTracesSamplerArg)
36
-
37
- if (!(ddVar in counters)) counters[ddVar] = {}
43
+ if (!(otelVar in counters)) counters[otelVar] = {}
38
44
 
39
45
  const counter = tracerMetrics.count(event, tags)
40
- counters[ddVar][otelVar] = counter
46
+ counters[otelVar][ddVar] = counter
41
47
  return counter
42
48
  }
43
49
 
44
50
  const otelDdEnvMapping = {
45
- DD_TRACE_LOG_LEVEL: 'OTEL_LOG_LEVEL',
46
- DD_TRACE_PROPAGATION_STYLE: 'OTEL_PROPAGATORS',
47
- DD_SERVICE: 'OTEL_SERVICE_NAME',
48
- DD_TRACE_SAMPLE_RATE: 'OTEL_TRACES_SAMPLER',
49
- DD_TRACE_ENABLED: 'OTEL_TRACES_EXPORTER',
50
- DD_RUNTIME_METRICS_ENABLED: 'OTEL_METRICS_EXPORTER',
51
- DD_TAGS: 'OTEL_RESOURCE_ATTRIBUTES',
52
- DD_TRACE_OTEL_ENABLED: 'OTEL_SDK_DISABLED'
51
+ OTEL_LOG_LEVEL: 'DD_TRACE_LOG_LEVEL',
52
+ OTEL_PROPAGATORS: 'DD_TRACE_PROPAGATION_STYLE',
53
+ OTEL_SERVICE_NAME: 'DD_SERVICE',
54
+ OTEL_TRACES_SAMPLER: 'DD_TRACE_SAMPLE_RATE',
55
+ OTEL_TRACES_SAMPLER_ARG: 'DD_TRACE_SAMPLE_RATE',
56
+ OTEL_TRACES_EXPORTER: 'DD_TRACE_ENABLED',
57
+ OTEL_METRICS_EXPORTER: 'DD_RUNTIME_METRICS_ENABLED',
58
+ OTEL_RESOURCE_ATTRIBUTES: 'DD_TAGS',
59
+ OTEL_SDK_DISABLED: 'DD_TRACE_OTEL_ENABLED',
60
+ OTEL_LOGS_EXPORTER: undefined
53
61
  }
54
62
 
55
- const otelInvalidEnv = ['OTEL_LOGS_EXPORTER']
63
+ const VALID_PROPAGATION_STYLES = new Set(['datadog', 'tracecontext', 'b3', 'b3 single header', 'none'])
56
64
 
57
- function checkIfBothOtelAndDdEnvVarSet () {
58
- for (const [ddVar, otelVar] of Object.entries(otelDdEnvMapping)) {
59
- if (process.env[ddVar] && process.env[otelVar]) {
60
- log.warn(`both ${ddVar} and ${otelVar} environment variables are set`)
61
- getCounter('otel.env.hiding', ddVar, otelVar,
62
- otelVar === 'OTEL_TRACES_SAMPLER' &&
63
- process.env.OTEL_TRACES_SAMPLER_ARG
64
- ? 'OTEL_TRACES_SAMPLER_ARG'
65
- : undefined).inc()
65
+ const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error'])
66
+
67
+ function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) {
68
+ const OTEL_TRACES_SAMPLER_MAPPING = {
69
+ always_on: '1.0',
70
+ always_off: '0.0',
71
+ traceidratio: otelTracesSamplerArg,
72
+ parentbased_always_on: '1.0',
73
+ parentbased_always_off: '0.0',
74
+ parentbased_traceidratio: otelTracesSamplerArg
75
+ }
76
+ return OTEL_TRACES_SAMPLER_MAPPING[otelTracesSampler]
77
+ }
78
+
79
+ function validateOtelPropagators (propagators) {
80
+ if (!process.env.PROPAGATION_STYLE_EXTRACT &&
81
+ !process.env.PROPAGATION_STYLE_INJECT &&
82
+ !process.env.DD_TRACE_PROPAGATION_STYLE &&
83
+ process.env.OTEL_PROPAGATORS) {
84
+ for (const style in propagators) {
85
+ if (!VALID_PROPAGATION_STYLES.has(style)) {
86
+ log.warn('unexpected value for OTEL_PROPAGATORS environment variable')
87
+ getCounter('otel.env.invalid', 'DD_TRACE_PROPAGATION_STYLE', 'OTEL_PROPAGATORS').inc()
88
+ }
66
89
  }
67
90
  }
91
+ }
68
92
 
69
- for (const otelVar of otelInvalidEnv) {
70
- if (process.env[otelVar]) {
71
- log.warn(`${otelVar} is not supported by the Datadog SDK`)
72
- getCounter('otel.env.invalid', otelVar).inc()
93
+ function validateEnvVarType (envVar) {
94
+ const value = process.env[envVar]
95
+ switch (envVar) {
96
+ case 'OTEL_LOG_LEVEL':
97
+ return VALID_LOG_LEVELS.has(value)
98
+ case 'OTEL_PROPAGATORS':
99
+ case 'OTEL_RESOURCE_ATTRIBUTES':
100
+ case 'OTEL_SERVICE_NAME':
101
+ return typeof value === 'string'
102
+ case 'OTEL_TRACES_SAMPLER':
103
+ return getFromOtelSamplerMap(value, process.env.OTEL_TRACES_SAMPLER_ARG) !== undefined
104
+ case 'OTEL_TRACES_SAMPLER_ARG':
105
+ return !isNaN(parseFloat(value))
106
+ case 'OTEL_SDK_DISABLED':
107
+ return value.toLowerCase() === 'true' || value.toLowerCase() === 'false'
108
+ case 'OTEL_TRACES_EXPORTER':
109
+ case 'OTEL_METRICS_EXPORTER':
110
+ case 'OTEL_LOGS_EXPORTER':
111
+ return value.toLowerCase() === 'none'
112
+ default:
113
+ return false
114
+ }
115
+ }
116
+
117
+ function checkIfBothOtelAndDdEnvVarSet () {
118
+ for (const [otelEnvVar, ddEnvVar] of Object.entries(otelDdEnvMapping)) {
119
+ if (ddEnvVar && process.env[ddEnvVar] && process.env[otelEnvVar]) {
120
+ log.warn(`both ${ddEnvVar} and ${otelEnvVar} environment variables are set`)
121
+ getCounter('otel.env.hiding', ddEnvVar, otelEnvVar).inc()
122
+ }
123
+
124
+ if (process.env[otelEnvVar] && !validateEnvVarType(otelEnvVar)) {
125
+ log.warn(`unexpected value for ${otelEnvVar} environment variable`)
126
+ getCounter('otel.env.invalid', ddEnvVar, otelEnvVar).inc()
73
127
  }
74
128
  }
75
129
  }
@@ -80,9 +134,9 @@ const fromEntries = Object.fromEntries || (entries =>
80
134
  // eslint-disable-next-line max-len
81
135
  const qsRegex = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\\s|%20)*(?::|%3A)(?:\\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|bearer(?:\\s|%20)+[a-z0-9\\._\\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\\w=-]|%3D)+\\.ey[I-L](?:[\\w=-]|%3D)+(?:\\.(?:[\\w.+\\/=-]|%3D|%2F|%2B)+)?|[\\-]{5}BEGIN(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY[\\-]{5}[^\\-]+[\\-]{5}END(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY|ssh-rsa(?:\\s|%20)*(?:[a-z0-9\\/\\.+]|%2F|%5C|%2B){100,}'
82
136
  // eslint-disable-next-line max-len
83
- const defaultWafObfuscatorKeyRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization'
137
+ const defaultWafObfuscatorKeyRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key)|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\\.net[_-]sessionid|sid|jwt'
84
138
  // eslint-disable-next-line max-len
85
- const defaultWafObfuscatorValueRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\\s*=[^;]|"\\s*:\\s*"[^"]+")|bearer\\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(?:\\.[\\w.+\\/=-]+)?|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}'
139
+ const defaultWafObfuscatorValueRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|jsessionid|phpsessid|asp\\.net(?:[_-]|-)sessionid|sid|jwt)(?:\\s*=[^;]|"\\s*:\\s*"[^"]+")|bearer\\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(?:\\.[\\w.+\\/=-]+)?|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}'
86
140
  const runtimeId = uuid()
87
141
 
88
142
  function maybeFile (filepath) {
@@ -235,6 +289,9 @@ class Config {
235
289
  options.tracePropagationStyle,
236
290
  defaultPropagationStyle
237
291
  )
292
+
293
+ validateOtelPropagators(PROPAGATION_STYLE_INJECT)
294
+
238
295
  const DD_TRACE_PROPAGATION_EXTRACT_FIRST = coalesce(
239
296
  process.env.DD_TRACE_PROPAGATION_EXTRACT_FIRST,
240
297
  false
@@ -440,6 +497,10 @@ class Config {
440
497
  this._setValue(defaults, 'appsec.rateLimit', 100)
441
498
  this._setValue(defaults, 'appsec.rules', undefined)
442
499
  this._setValue(defaults, 'appsec.sca.enabled', null)
500
+ this._setValue(defaults, 'appsec.standalone.enabled', undefined)
501
+ this._setValue(defaults, 'appsec.stackTrace.enabled', true)
502
+ this._setValue(defaults, 'appsec.stackTrace.maxDepth', 32)
503
+ this._setValue(defaults, 'appsec.stackTrace.maxStackTraces', 2)
443
504
  this._setValue(defaults, 'appsec.wafTimeout', 5e3) // µs
444
505
  this._setValue(defaults, 'clientIpEnabled', false)
445
506
  this._setValue(defaults, 'clientIpHeader', null)
@@ -524,10 +585,13 @@ class Config {
524
585
  DD_APPSEC_ENABLED,
525
586
  DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML,
526
587
  DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON,
588
+ DD_APPSEC_MAX_STACK_TRACES,
589
+ DD_APPSEC_MAX_STACK_TRACE_DEPTH,
527
590
  DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
528
591
  DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
529
592
  DD_APPSEC_RULES,
530
593
  DD_APPSEC_SCA_ENABLED,
594
+ DD_APPSEC_STACK_TRACE_ENABLED,
531
595
  DD_APPSEC_RASP_ENABLED,
532
596
  DD_APPSEC_TRACE_RATE_LIMIT,
533
597
  DD_APPSEC_WAF_TIMEOUT,
@@ -536,6 +600,7 @@ class Config {
536
600
  DD_DOGSTATSD_HOSTNAME,
537
601
  DD_DOGSTATSD_PORT,
538
602
  DD_ENV,
603
+ DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED,
539
604
  DD_EXPERIMENTAL_PROFILING_ENABLED,
540
605
  JEST_WORKER_ID,
541
606
  DD_IAST_DEDUPLICATION_ENABLED,
@@ -627,6 +692,12 @@ class Config {
627
692
  this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
628
693
  // DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
629
694
  this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
695
+ this._setBoolean(env, 'appsec.standalone.enabled', DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED)
696
+ this._setBoolean(env, 'appsec.stackTrace.enabled', DD_APPSEC_STACK_TRACE_ENABLED)
697
+ this._setValue(env, 'appsec.stackTrace.maxDepth', maybeInt(DD_APPSEC_MAX_STACK_TRACE_DEPTH))
698
+ this._envUnprocessed['appsec.stackTrace.maxDepth'] = DD_APPSEC_MAX_STACK_TRACE_DEPTH
699
+ this._setValue(env, 'appsec.stackTrace.maxStackTraces', maybeInt(DD_APPSEC_MAX_STACK_TRACES))
700
+ this._envUnprocessed['appsec.stackTrace.maxStackTraces'] = DD_APPSEC_MAX_STACK_TRACES
630
701
  this._setValue(env, 'appsec.wafTimeout', maybeInt(DD_APPSEC_WAF_TIMEOUT))
631
702
  this._envUnprocessed['appsec.wafTimeout'] = DD_APPSEC_WAF_TIMEOUT
632
703
  this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
@@ -701,15 +772,8 @@ class Config {
701
772
  : undefined
702
773
  this._setBoolean(env, 'runtimeMetrics', DD_RUNTIME_METRICS_ENABLED ||
703
774
  otelSetRuntimeMetrics)
704
- const OTEL_TRACES_SAMPLER_MAPPING = {
705
- always_on: '1.0',
706
- always_off: '0.0',
707
- traceidratio: OTEL_TRACES_SAMPLER_ARG,
708
- parentbased_always_on: '1.0',
709
- parentbased_always_off: '0.0',
710
- parentbased_traceidratio: OTEL_TRACES_SAMPLER_ARG
711
- }
712
- this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE || OTEL_TRACES_SAMPLER_MAPPING[OTEL_TRACES_SAMPLER])
775
+ this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE ||
776
+ getFromOtelSamplerMap(OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG))
713
777
  this._setValue(env, 'sampler.rateLimit', DD_TRACE_RATE_LIMIT)
714
778
  this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES))
715
779
  this._envUnprocessed['sampler.rules'] = DD_TRACE_SAMPLING_RULES
@@ -767,6 +831,12 @@ class Config {
767
831
  this._setValue(opts, 'appsec.rateLimit', maybeInt(options.appsec.rateLimit))
768
832
  this._optsUnprocessed['appsec.rateLimit'] = options.appsec.rateLimit
769
833
  this._setString(opts, 'appsec.rules', options.appsec.rules)
834
+ this._setBoolean(opts, 'appsec.standalone.enabled', options.experimental?.appsec?.standalone?.enabled)
835
+ this._setBoolean(opts, 'appsec.stackTrace.enabled', options.appsec.stackTrace?.enabled)
836
+ this._setValue(opts, 'appsec.stackTrace.maxDepth', maybeInt(options.appsec.stackTrace?.maxDepth))
837
+ this._optsUnprocessed['appsec.stackTrace.maxDepth'] = options.appsec.stackTrace?.maxDepth
838
+ this._setValue(opts, 'appsec.stackTrace.maxStackTraces', maybeInt(options.appsec.stackTrace?.maxStackTraces))
839
+ this._optsUnprocessed['appsec.stackTrace.maxStackTraces'] = options.appsec.stackTrace?.maxStackTraces
770
840
  this._setValue(opts, 'appsec.wafTimeout', maybeInt(options.appsec.wafTimeout))
771
841
  this._optsUnprocessed['appsec.wafTimeout'] = options.appsec.wafTimeout
772
842
  this._setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled)
@@ -32,5 +32,7 @@ module.exports = {
32
32
  PEER_SERVICE_SOURCE_KEY: '_dd.peer.service.source',
33
33
  PEER_SERVICE_REMAP_KEY: '_dd.peer.service.remapped_from',
34
34
  SCI_REPOSITORY_URL: '_dd.git.repository_url',
35
- SCI_COMMIT_SHA: '_dd.git.commit.sha'
35
+ SCI_COMMIT_SHA: '_dd.git.commit.sha',
36
+ APM_TRACING_ENABLED_KEY: '_dd.apm.enabled',
37
+ APPSEC_PROPAGATION_KEY: '_dd.p.appsec'
36
38
  }
@@ -211,7 +211,8 @@ class DataStreamsProcessor {
211
211
  Stats,
212
212
  TracerVersion: pkg.version,
213
213
  Version: this.version,
214
- Lang: 'javascript'
214
+ Lang: 'javascript',
215
+ Tags: Object.entries(this.tags).map(([key, value]) => `${key}:${value}`)
215
216
  }
216
217
  this.writer.flush(payload)
217
218
  }
@@ -7,7 +7,7 @@ const Writer = require('./writer')
7
7
  class AgentExporter {
8
8
  constructor (config, prioritySampler) {
9
9
  this._config = config
10
- const { url, hostname, port, lookup, protocolVersion, stats = {} } = config
10
+ const { url, hostname, port, lookup, protocolVersion, stats = {}, appsec } = config
11
11
  this._url = url || new URL(format({
12
12
  protocol: 'http:',
13
13
  hostname: hostname || 'localhost',
@@ -15,7 +15,7 @@ class AgentExporter {
15
15
  }))
16
16
 
17
17
  const headers = {}
18
- if (stats.enabled) {
18
+ if (stats.enabled || appsec?.standalone?.enabled) {
19
19
  headers['Datadog-Client-Computed-Stats'] = 'yes'
20
20
  }
21
21
 
@@ -53,6 +53,7 @@ function formatSpan (span) {
53
53
  resource: String(spanContext._name),
54
54
  error: 0,
55
55
  meta: {},
56
+ meta_struct: span.meta_struct,
56
57
  metrics: {},
57
58
  start: Math.round(span._startTime * 1e6),
58
59
  duration: Math.round(span._duration * 1e6),
@@ -6,9 +6,13 @@ const DatadogSpanContext = require('../span_context')
6
6
  const log = require('../../log')
7
7
  const TraceState = require('./tracestate')
8
8
  const tags = require('../../../../../ext/tags')
9
+ const { channel } = require('dc-polyfill')
9
10
 
10
11
  const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
11
12
 
13
+ const injectCh = channel('dd-trace:span:inject')
14
+ const extractCh = channel('dd-trace:span:extract')
15
+
12
16
  const traceKey = 'x-datadog-trace-id'
13
17
  const spanKey = 'x-datadog-parent-id'
14
18
  const originKey = 'x-datadog-origin'
@@ -54,6 +58,10 @@ class TextMapPropagator {
54
58
  this._injectB3SingleHeader(spanContext, carrier)
55
59
  this._injectTraceparent(spanContext, carrier)
56
60
 
61
+ if (injectCh.hasSubscribers) {
62
+ injectCh.publish({ spanContext, carrier })
63
+ }
64
+
57
65
  log.debug(() => `Inject into carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
58
66
  }
59
67
 
@@ -62,6 +70,10 @@ class TextMapPropagator {
62
70
 
63
71
  if (!spanContext) return spanContext
64
72
 
73
+ if (extractCh.hasSubscribers) {
74
+ extractCh.publish({ spanContext, carrier })
75
+ }
76
+
65
77
  log.debug(() => `Extract from carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
66
78
 
67
79
  return spanContext
@@ -99,7 +99,10 @@ class DatadogSpan {
99
99
  unfinishedRegistry.register(this, operationName, this)
100
100
  }
101
101
  spanleak.addSpan(this, operationName)
102
- startCh.publish(this)
102
+
103
+ if (startCh.hasSubscribers) {
104
+ startCh.publish({ span: this, fields })
105
+ }
103
106
  }
104
107
 
105
108
  toString () {
@@ -19,7 +19,7 @@ const REFERENCE_CHILD_OF = 'child_of'
19
19
  const REFERENCE_FOLLOWS_FROM = 'follows_from'
20
20
 
21
21
  class DatadogTracer {
22
- constructor (config) {
22
+ constructor (config, prioritySampler) {
23
23
  const Exporter = getExporter(config.experimental.exporter)
24
24
 
25
25
  this._config = config
@@ -28,7 +28,7 @@ class DatadogTracer {
28
28
  this._env = config.env
29
29
  this._logInjection = config.logInjection
30
30
  this._debug = config.debug
31
- this._prioritySampler = new PrioritySampler(config.env, config.sampler)
31
+ this._prioritySampler = prioritySampler ?? new PrioritySampler(config.env, config.sampler)
32
32
  this._exporter = new Exporter(config, this._prioritySampler)
33
33
  this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config)
34
34
  this._url = this._exporter._url
@@ -91,6 +91,13 @@ module.exports = class CiPlugin extends Plugin {
91
91
  ...testModuleSpanMetadata
92
92
  }
93
93
  })
94
+ // only for vitest
95
+ // These are added for the worker threads to use
96
+ if (this.constructor.id === 'vitest') {
97
+ process.env.DD_CIVISIBILITY_TEST_SESSION_ID = this.testSessionSpan.context().toTraceId()
98
+ process.env.DD_CIVISIBILITY_TEST_MODULE_ID = this.testModuleSpan.context().toSpanId()
99
+ }
100
+
94
101
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'module')
95
102
  })
96
103
 
@@ -18,6 +18,7 @@ module.exports = {
18
18
  get '@opensearch-project/opensearch' () { return require('../../../datadog-plugin-opensearch/src') },
19
19
  get '@redis/client' () { return require('../../../datadog-plugin-redis/src') },
20
20
  get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
21
+ get '@vitest/runner' () { return require('../../../datadog-plugin-vitest/src') },
21
22
  get aerospike () { return require('../../../datadog-plugin-aerospike/src') },
22
23
  get amqp10 () { return require('../../../datadog-plugin-amqp10/src') },
23
24
  get amqplib () { return require('../../../datadog-plugin-amqplib/src') },
@@ -54,6 +55,7 @@ module.exports = {
54
55
  get 'microgateway-core' () { return require('../../../datadog-plugin-microgateway-core/src') },
55
56
  get mocha () { return require('../../../datadog-plugin-mocha/src') },
56
57
  get 'mocha-each' () { return require('../../../datadog-plugin-mocha/src') },
58
+ get vitest () { return require('../../../datadog-plugin-vitest/src') },
57
59
  get workerpool () { return require('../../../datadog-plugin-mocha/src') },
58
60
  get moleculer () { return require('../../../datadog-plugin-moleculer/src') },
59
61
  get mongodb () { return require('../../../datadog-plugin-mongodb-core/src') },
@@ -95,6 +95,9 @@ const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
95
95
  const EFD_STRING = "Retried by Datadog's Early Flake Detection"
96
96
  const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
97
97
 
98
+ // Flaky test retries
99
+ const NUM_FAILED_TEST_RETRIES = 5
100
+
98
101
  module.exports = {
99
102
  TEST_CODE_OWNERS,
100
103
  TEST_FRAMEWORK,
@@ -167,7 +170,8 @@ module.exports = {
167
170
  TEST_BROWSER_DRIVER,
168
171
  TEST_BROWSER_DRIVER_VERSION,
169
172
  TEST_BROWSER_NAME,
170
- TEST_BROWSER_VERSION
173
+ TEST_BROWSER_VERSION,
174
+ NUM_FAILED_TEST_RETRIES
171
175
  }
172
176
 
173
177
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -4,6 +4,7 @@ const RateLimiter = require('./rate_limiter')
4
4
  const Sampler = require('./sampler')
5
5
  const { setSamplingRules } = require('./startup-log')
6
6
  const SamplingRule = require('./sampling_rule')
7
+ const { hasOwn } = require('./util')
7
8
 
8
9
  const {
9
10
  SAMPLING_MECHANISM_DEFAULT,
@@ -66,7 +67,7 @@ class PrioritySampler {
66
67
  if (context._sampling.priority !== undefined) return
67
68
  if (!root) return // noop span
68
69
 
69
- const tag = this._getPriorityFromTags(context._tags)
70
+ const tag = this._getPriorityFromTags(context._tags, context)
70
71
 
71
72
  if (this.validate(tag)) {
72
73
  context._sampling.priority = tag
@@ -202,8 +203,4 @@ class PrioritySampler {
202
203
  }
203
204
  }
204
205
 
205
- function hasOwn (object, prop) {
206
- return Object.prototype.hasOwnProperty.call(object, prop)
207
- }
208
-
209
206
  module.exports = PrioritySampler
@@ -69,7 +69,7 @@ class Profiler extends EventEmitter {
69
69
  setLogger(config.logger)
70
70
 
71
71
  mapper = await maybeSourceMap(config.sourceMap, SourceMapper, config.debugSourceMaps)
72
- if (config.SourceMap && config.debugSourceMaps) {
72
+ if (config.sourceMap && config.debugSourceMaps) {
73
73
  this._logger.debug(() => {
74
74
  return mapper.infoMap.size === 0
75
75
  ? 'Found no source maps'
@@ -15,6 +15,7 @@ const NoopDogStatsDClient = require('./noop/dogstatsd')
15
15
  const spanleak = require('./spanleak')
16
16
  const { SSIHeuristics } = require('./profiling/ssi-heuristics')
17
17
  const telemetryLog = require('dc-polyfill').channel('datadog:telemetry:log')
18
+ const appsecStandalone = require('./appsec/standalone')
18
19
 
19
20
  class LazyModule {
20
21
  constructor (provider) {
@@ -178,7 +179,8 @@ class Tracer extends NoopProxy {
178
179
  this._modules.appsec.enable(config)
179
180
  }
180
181
  if (!this._tracingInitialized) {
181
- this._tracer = new DatadogTracer(config)
182
+ const prioritySampler = appsecStandalone.configure(config)
183
+ this._tracer = new DatadogTracer(config, prioritySampler)
182
184
  this.appsec = new AppsecSdk(this._tracer, config)
183
185
  this._tracingInitialized = true
184
186
  }
@@ -3,9 +3,9 @@
3
3
  const limiter = require('limiter')
4
4
 
5
5
  class RateLimiter {
6
- constructor (rateLimit) {
6
+ constructor (rateLimit, interval = 'second') {
7
7
  this._rateLimit = parseInt(rateLimit)
8
- this._limiter = new limiter.RateLimiter(this._rateLimit, 'second')
8
+ this._limiter = new limiter.RateLimiter(this._rateLimit, interval)
9
9
  this._tokensRequested = 0
10
10
  this._prevIntervalTokens = 0
11
11
  this._prevTokensRequested = 0
@@ -126,7 +126,8 @@ class SpanStatsProcessor {
126
126
  port,
127
127
  url,
128
128
  env,
129
- tags
129
+ tags,
130
+ appsec
130
131
  } = {}) {
131
132
  this.exporter = new SpanStatsExporter({
132
133
  hostname,
@@ -138,12 +139,12 @@ class SpanStatsProcessor {
138
139
  this.bucketSizeNs = interval * 1e9
139
140
  this.buckets = new TimeBuckets()
140
141
  this.hostname = os.hostname()
141
- this.enabled = enabled
142
+ this.enabled = enabled && !appsec?.standalone?.enabled
142
143
  this.env = env
143
144
  this.tags = tags || {}
144
145
  this.sequence = 0
145
146
 
146
- if (enabled) {
147
+ if (this.enabled) {
147
148
  this.timer = setInterval(this.onInterval.bind(this), interval * 1e3)
148
149
  this.timer.unref()
149
150
  }