dd-trace 6.11.0 → 6.13.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 (139) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  3. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  4. package/index.d.ts +143 -25
  5. package/package.json +26 -25
  6. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  7. package/packages/datadog-instrumentations/src/cucumber.js +4 -4
  8. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  9. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  12. package/packages/datadog-instrumentations/src/jest.js +140 -18
  13. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  14. package/packages/datadog-instrumentations/src/mariadb.js +7 -0
  15. package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
  16. package/packages/datadog-instrumentations/src/pino.js +15 -4
  17. package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
  18. package/packages/datadog-instrumentations/src/playwright.js +67 -53
  19. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +13 -12
  20. package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
  21. package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
  22. package/packages/datadog-instrumentations/src/winston.js +6 -3
  23. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  24. package/packages/datadog-plugin-http/src/client.js +2 -15
  25. package/packages/datadog-plugin-http2/src/client.js +2 -15
  26. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  27. package/packages/datadog-plugin-playwright/src/index.js +13 -1
  28. package/packages/datadog-plugin-undici/src/index.js +2 -15
  29. package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
  30. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  31. package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
  32. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  33. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  34. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  35. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  37. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  38. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  39. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  40. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  41. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  42. package/packages/dd-trace/src/carrier.js +107 -48
  43. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  44. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
  46. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
  47. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
  49. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  50. package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
  51. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
  52. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
  53. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  54. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  55. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +4 -2
  56. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  57. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
  58. package/packages/dd-trace/src/config/index.js +2 -1
  59. package/packages/dd-trace/src/config/supported-configurations.json +27 -0
  60. package/packages/dd-trace/src/debugger/constants.js +1 -0
  61. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  62. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -10
  63. package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
  64. package/packages/dd-trace/src/debugger/index.js +4 -2
  65. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  66. package/packages/dd-trace/src/dogstatsd.js +45 -18
  67. package/packages/dd-trace/src/encode/agentless-json.js +5 -4
  68. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  69. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  70. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  71. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  72. package/packages/dd-trace/src/exporters/agent/index.js +22 -2
  73. package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
  74. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  75. package/packages/dd-trace/src/exporters/common/writer.js +24 -2
  76. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  77. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  78. package/packages/dd-trace/src/flush.js +102 -0
  79. package/packages/dd-trace/src/llmobs/experiments/client.js +10 -2
  80. package/packages/dd-trace/src/llmobs/experiments/dataset.js +301 -14
  81. package/packages/dd-trace/src/llmobs/experiments/experiment.js +440 -112
  82. package/packages/dd-trace/src/llmobs/experiments/index.js +68 -44
  83. package/packages/dd-trace/src/llmobs/experiments/noop.js +103 -12
  84. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  85. package/packages/dd-trace/src/llmobs/experiments/util.js +81 -4
  86. package/packages/dd-trace/src/llmobs/index.js +80 -17
  87. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  88. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  89. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  90. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  91. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  92. package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
  93. package/packages/dd-trace/src/noop/span.js +5 -0
  94. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  95. package/packages/dd-trace/src/openfeature/index.js +7 -4
  96. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  97. package/packages/dd-trace/src/openfeature/writers/exposures.js +70 -15
  98. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  99. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  100. package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
  101. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  102. package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
  103. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  104. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
  105. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
  106. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  107. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  108. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  109. package/packages/dd-trace/src/opentracing/propagation/text_map.js +429 -352
  110. package/packages/dd-trace/src/opentracing/span.js +28 -0
  111. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  112. package/packages/dd-trace/src/plugin_manager.js +2 -0
  113. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  114. package/packages/dd-trace/src/plugins/util/test.js +74 -21
  115. package/packages/dd-trace/src/plugins/util/web.js +2 -65
  116. package/packages/dd-trace/src/profiling/oom.js +4 -3
  117. package/packages/dd-trace/src/profiling/profiler.js +7 -0
  118. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  119. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  120. package/packages/dd-trace/src/proxy.js +36 -8
  121. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  122. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
  123. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
  124. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
  125. package/packages/dd-trace/src/serverless/vercel.js +122 -0
  126. package/packages/dd-trace/src/serverless.js +37 -20
  127. package/packages/dd-trace/src/span_stats.js +40 -3
  128. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  129. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  130. package/packages/dd-trace/src/tracer.js +22 -0
  131. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  132. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  133. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  134. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  135. package/vendor/dist/https-proxy-agent/index.js +4 -0
  136. package/vendor/dist/protobufjs/index.js +1 -1
  137. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  138. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  139. package/vendor/dist/proxy-from-env/index.js +1 -0
@@ -0,0 +1,183 @@
1
+ 'use strict'
2
+
3
+ /** @typedef {import('../../../../index').aiguard.RedactionReplacement} RedactionReplacement */
4
+
5
+ const SEGMENT_PATTERN = /^([A-Za-z0-9_]+)(?:\[([0-9]+)\])?$/
6
+
7
+ /**
8
+ * Converts the raw backend replacements into the public, typed contract.
9
+ *
10
+ * @param {unknown} replacements
11
+ * @returns {RedactionReplacement[]}
12
+ */
13
+ function normalizeRedactionReplacements (replacements) {
14
+ if (!Array.isArray(replacements)) return []
15
+
16
+ const result = []
17
+ for (const entry of replacements) {
18
+ if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) continue
19
+
20
+ const { path, replacement } = entry
21
+ if (typeof path !== 'string' || path.length === 0 || typeof replacement !== 'string') continue
22
+
23
+ result.push({ path, replacement })
24
+ }
25
+ return result
26
+ }
27
+
28
+ /**
29
+ * Parses an AI Guard location path into named segments and optional indexes.
30
+ *
31
+ * @param {string} path
32
+ * @returns {Array<{ name: string, index: number|undefined }>|undefined}
33
+ */
34
+ function parsePath (path) {
35
+ const segments = []
36
+ for (const rawSegment of path.split('.')) {
37
+ const match = SEGMENT_PATTERN.exec(rawSegment)
38
+ if (!match) return
39
+
40
+ segments.push({
41
+ name: match[1],
42
+ index: match[2] === undefined ? undefined : Number(match[2]),
43
+ })
44
+ }
45
+ return segments
46
+ }
47
+
48
+ /**
49
+ * Reports whether a parsed path targets an allowed message string.
50
+ *
51
+ * @param {Array<{ name: string, index: number|undefined }>} segments
52
+ * @returns {boolean}
53
+ */
54
+ function isRedactablePath (segments) {
55
+ const first = segments[0]
56
+ if (first?.name !== 'messages' || first.index === undefined) return false
57
+
58
+ if (segments.length === 2) {
59
+ const content = segments[1]
60
+ return content.name === 'content' && content.index === undefined
61
+ }
62
+
63
+ if (segments.length === 3) {
64
+ const content = segments[1]
65
+ const text = segments[2]
66
+ return content.name === 'content' && content.index !== undefined &&
67
+ text.name === 'text' && text.index === undefined
68
+ }
69
+
70
+ if (segments.length === 4) {
71
+ const toolCalls = segments[1]
72
+ const functionSegment = segments[2]
73
+ const argumentsSegment = segments[3]
74
+ return toolCalls.name === 'tool_calls' && toolCalls.index !== undefined &&
75
+ functionSegment.name === 'function' && functionSegment.index === undefined &&
76
+ argumentsSegment.name === 'arguments' && argumentsSegment.index === undefined
77
+ }
78
+
79
+ return false
80
+ }
81
+
82
+ /**
83
+ * Resolves a location path to a writable string container and key.
84
+ *
85
+ * @param {{ messages: Array<object> }} root
86
+ * @param {string} path
87
+ * @returns {{ container: object|Array<unknown>, key: string|number }|undefined}
88
+ */
89
+ function resolveWritableString (root, path) {
90
+ const segments = parsePath(path)
91
+ if (!segments || !isRedactablePath(segments)) return
92
+
93
+ const terminal = segments.at(-1)
94
+ let node = root
95
+ for (let i = 0; i < segments.length - 1; i++) {
96
+ const { name, index } = segments[i]
97
+ if (!node || typeof node !== 'object' || !Object.hasOwn(node, name)) return
98
+
99
+ node = node[name]
100
+ if (index !== undefined) {
101
+ if (!Array.isArray(node) || index >= node.length) return
102
+ node = node[index]
103
+ }
104
+ }
105
+
106
+ if (!node || typeof node !== 'object') return
107
+ if (!Object.hasOwn(node, terminal.name) || typeof node[terminal.name] !== 'string') return
108
+ return { container: node, key: terminal.name }
109
+ }
110
+
111
+ /**
112
+ * Applies redaction replacements to an AI Guard message list.
113
+ * The caller transfers ownership of a private snapshot. Successful replacements mutate that snapshot in place.
114
+ *
115
+ * @param {Array<object>} messages
116
+ * @param {unknown} replacements
117
+ * @returns {{ messages: Array<object>, redacted: boolean, failures: number }}
118
+ */
119
+ function redactMessages (messages, replacements) {
120
+ if (!Array.isArray(messages) || replacements === undefined || replacements === null) {
121
+ return { messages, redacted: false, failures: 0 }
122
+ }
123
+
124
+ try {
125
+ if (!Array.isArray(replacements)) {
126
+ return { messages, redacted: false, failures: 1 }
127
+ }
128
+ if (replacements.length === 0) return { messages, redacted: false, failures: 0 }
129
+
130
+ const replacementsByPath = new Map()
131
+ const conflictingPaths = new Set()
132
+ let failures = 0
133
+ for (const entry of replacements) {
134
+ if (!entry || typeof entry !== 'object') {
135
+ failures++
136
+ continue
137
+ }
138
+
139
+ const { path, replacement } = entry
140
+ if (typeof path !== 'string' || path.length === 0 || typeof replacement !== 'string') {
141
+ failures++
142
+ continue
143
+ }
144
+
145
+ if (conflictingPaths.has(path)) continue
146
+ if (replacementsByPath.has(path) && replacementsByPath.get(path) !== replacement) {
147
+ replacementsByPath.delete(path)
148
+ conflictingPaths.add(path)
149
+ failures++
150
+ continue
151
+ }
152
+ replacementsByPath.set(path, replacement)
153
+ }
154
+
155
+ if (replacementsByPath.size === 0) return { messages, redacted: false, failures }
156
+
157
+ const root = { messages }
158
+ const targets = []
159
+
160
+ for (const [path, replacement] of replacementsByPath) {
161
+ const resolved = resolveWritableString(root, path)
162
+ if (!resolved) {
163
+ failures++
164
+ continue
165
+ }
166
+
167
+ targets.push({ ...resolved, replacement })
168
+ }
169
+
170
+ for (const { container, key, replacement } of targets) {
171
+ container[key] = replacement
172
+ }
173
+
174
+ return { messages, redacted: targets.length > 0, failures }
175
+ } catch {
176
+ return { messages, redacted: false, failures: 1 }
177
+ }
178
+ }
179
+
180
+ module.exports = {
181
+ normalizeRedactionReplacements,
182
+ redactMessages,
183
+ }
@@ -12,6 +12,7 @@ class AIGuard extends NoopAIGuard {
12
12
  #tracer
13
13
  #client
14
14
  #reporter
15
+ #redactionEnabled
15
16
  #meta
16
17
 
17
18
  /**
@@ -29,6 +30,7 @@ class AIGuard extends NoopAIGuard {
29
30
  this.#tracer = tracer
30
31
  this.#client = new AIGuardClient(config)
31
32
  this.#reporter = new EvaluationReporter(config)
33
+ this.#redactionEnabled = config.experimental.aiguard.redactionEnabled
32
34
  this.#meta = { service: config.service, env: config.env }
33
35
  this.#initialized = true
34
36
  }
@@ -45,13 +47,16 @@ class AIGuard extends NoopAIGuard {
45
47
  const report = this.#reporter.start(span, messages, { source, integration })
46
48
  let evaluation
47
49
  try {
48
- evaluation = await this.#client.evaluate(messages, this.#meta)
50
+ evaluation = await this.#client.evaluate(report.messages, this.#meta)
49
51
  } catch (error) {
50
52
  this.#reporter.fail(report, error.telemetryType ?? TAGS.ERROR_TYPE_CLIENT)
51
53
  throw error
52
54
  }
53
55
 
54
- const outcome = createEvaluationOutcome(evaluation, block)
56
+ const outcome = createEvaluationOutcome(report.messages, evaluation, {
57
+ block,
58
+ redactionEnabled: this.#redactionEnabled,
59
+ })
55
60
  this.#reporter.finish(report, outcome)
56
61
 
57
62
  if (outcome.shouldBlock) {
@@ -7,6 +7,7 @@ module.exports = {
7
7
  ACTION_TAG_KEY: 'ai_guard.action',
8
8
  REASON_TAG_KEY: 'ai_guard.reason',
9
9
  BLOCKED_TAG_KEY: 'ai_guard.blocked',
10
+ REDACTED_TAG_KEY: 'ai_guard.redacted',
10
11
  EVENT_TAG_KEY: 'ai_guard.event',
11
12
  META_STRUCT_KEY: 'ai_guard',
12
13
 
@@ -27,4 +28,5 @@ module.exports = {
27
28
  ERROR_TYPE_CLIENT: 'client_error',
28
29
  ERROR_TYPE_STATUS: 'bad_status',
29
30
  ERROR_TYPE_RESPONSE: 'bad_response',
31
+ ERROR_TYPE_REDACTION: 'redaction_error',
30
32
  }
@@ -108,7 +108,7 @@ function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) {
108
108
  value = JSON.stringify(cloneObj, null, 2)
109
109
 
110
110
  if (counter > 0) {
111
- const segments = []
111
+ let formattedValue = ''
112
112
  let outputLength = 0
113
113
  let pos = 0
114
114
  let rangeKeyIndex = value.indexOf(STRINGIFY_RANGE_KEY)
@@ -133,13 +133,14 @@ function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) {
133
133
  start,
134
134
  end: start + originalValue.length,
135
135
  })
136
- segments.push(value.slice(pos, rangeKeyIndex), originalValue)
136
+ formattedValue += value.slice(pos, rangeKeyIndex)
137
+ formattedValue += originalValue
137
138
  outputLength += cleanLength + originalValue.length
138
139
  pos = rangeKeyIndex + matchValue.length
139
140
  } else {
140
141
  // can't happen, the only way to this to happen is
141
142
  // if the JSON has a value starting with the value of STRINGIFY_SENSITIVE_NOT_STRING_KEY
142
- segments.push(value.slice(pos, rangeKeyIndex + STRINGIFY_SENSITIVE_NOT_STRING_KEY.length + 1))
143
+ formattedValue += value.slice(pos, rangeKeyIndex + STRINGIFY_SENSITIVE_NOT_STRING_KEY.length + 1)
143
144
  outputLength += cleanLength + STRINGIFY_SENSITIVE_NOT_STRING_KEY.length + 1
144
145
  pos = rangeKeyIndex + STRINGIFY_SENSITIVE_NOT_STRING_KEY.length + 1
145
146
  }
@@ -152,13 +153,13 @@ function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) {
152
153
  start,
153
154
  end: start + Number.parseInt(regexRes[1], 10),
154
155
  })
155
- segments.push(value.slice(pos, rangeKeyIndex))
156
+ formattedValue += value.slice(pos, rangeKeyIndex)
156
157
  outputLength += cleanLength
157
158
  pos = rangeKeyIndex + regexRes[0].length
158
159
  } else {
159
160
  // can't happen, the only way to this to happen is
160
161
  // if the JSON has a value starting with the value of STRINGIFY_SENSITIVE_KEY
161
- segments.push(value.slice(pos, rangeKeyIndex + STRINGIFY_SENSITIVE_KEY.length))
162
+ formattedValue += value.slice(pos, rangeKeyIndex + STRINGIFY_SENSITIVE_KEY.length)
162
163
  outputLength += cleanLength + STRINGIFY_SENSITIVE_KEY.length
163
164
  pos = rangeKeyIndex + STRINGIFY_SENSITIVE_KEY.length
164
165
  }
@@ -175,13 +176,13 @@ function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) {
175
176
  }))
176
177
  ranges.push(...updatedRanges)
177
178
 
178
- segments.push(value.slice(pos, rangeKeyIndex))
179
+ formattedValue += value.slice(pos, rangeKeyIndex)
179
180
  outputLength += cleanLength
180
181
  pos = rangeKeyIndex + regexRes[0].length
181
182
  } else {
182
183
  // can't happen, the only way to this to happen is
183
184
  // if the JSON has a value starting with the value of STRINGIFY_RANGE_KEY
184
- segments.push(value.slice(pos, rangeKeyIndex + STRINGIFY_RANGE_KEY.length))
185
+ formattedValue += value.slice(pos, rangeKeyIndex + STRINGIFY_RANGE_KEY.length)
185
186
  outputLength += cleanLength + STRINGIFY_RANGE_KEY.length
186
187
  pos = rangeKeyIndex + STRINGIFY_RANGE_KEY.length
187
188
  }
@@ -190,8 +191,8 @@ function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) {
190
191
  rangeKeyIndex = value.indexOf(STRINGIFY_RANGE_KEY, pos)
191
192
  }
192
193
 
193
- segments.push(value.slice(pos))
194
- value = segments.join('')
194
+ formattedValue += value.slice(pos)
195
+ value = formattedValue
195
196
  }
196
197
  } else {
197
198
  value = JSON.stringify(obj, null, 2)
@@ -15,6 +15,7 @@ const {
15
15
  incrementWafUpdatesMetric,
16
16
  incrementWafConfigErrorsMetric,
17
17
  incrementWafRequestsMetric,
18
+ incrementRequestDurationMetrics,
18
19
  updateWafRequestsMetricTags,
19
20
  updateRaspRequestsMetricTags,
20
21
  updateRaspRuleSkippedMetricTags,
@@ -608,6 +609,7 @@ function finishRequest (req, res, storedResponseHeaders, requestBody, rootSpan)
608
609
 
609
610
  if (!req) return
610
611
 
612
+ incrementRequestDurationMetrics(req)
611
613
  incrementWafRequestsMetric(req)
612
614
 
613
615
  const tags = rootSpan.context().getTags()
@@ -82,6 +82,8 @@ function updateWafFromRC (transaction) {
82
82
  const asmFile = /** @type {AsmConfigFile} */ (item.file)
83
83
  if (asmFile?.actions?.length) {
84
84
  newActions.set(item.id, asmFile.actions)
85
+ } else {
86
+ newActions.delete(item.id)
85
87
  }
86
88
  }
87
89
  } catch (e) {
@@ -12,6 +12,7 @@ const {
12
12
  trackRaspMetrics,
13
13
  trackRaspRuleMatch,
14
14
  trackRaspRuleSkipped,
15
+ incrementRaspDurationMetrics,
15
16
  } = require('./rasp')
16
17
  const {
17
18
  addWafRequestMetrics,
@@ -20,6 +21,7 @@ const {
20
21
  incrementWafUpdates,
21
22
  incrementWafConfigErrors,
22
23
  incrementWafRequests,
24
+ incrementWafDurationMetrics,
23
25
  } = require('./waf')
24
26
 
25
27
  const metricsStoreMap = new WeakMap()
@@ -142,6 +144,16 @@ function incrementWafRequestsMetric (req) {
142
144
  metricsStoreMap.delete(req)
143
145
  }
144
146
 
147
+ function incrementRequestDurationMetrics (req) {
148
+ if (!req || !enabled) return
149
+
150
+ const store = getStore(req)
151
+ const requestMetrics = store[DD_TELEMETRY_REQUEST_METRICS]
152
+
153
+ incrementWafDurationMetrics(requestMetrics)
154
+ incrementRaspDurationMetrics(requestMetrics)
155
+ }
156
+
145
157
  function incrementMissingUserLoginMetric (framework, eventType) {
146
158
  if (!enabled) return
147
159
 
@@ -199,6 +211,7 @@ module.exports = {
199
211
  incrementWafUpdatesMetric,
200
212
  incrementWafConfigErrorsMetric,
201
213
  incrementWafRequestsMetric,
214
+ incrementRequestDurationMetrics,
202
215
  incrementMissingUserLoginMetric,
203
216
  incrementMissingUserIdMetric,
204
217
  incrementSdkEventMetric,
@@ -77,6 +77,21 @@ function trackRaspRuleMatch (store, raspRule, blockTriggered, blocked) {
77
77
  appsecMetrics.count('rasp.rule.match', tags).inc(1)
78
78
  }
79
79
 
80
+ function incrementRaspDurationMetrics (requestMetrics) {
81
+ const { raspDuration, raspDurationExt, wafVersion, rulesVersion } = requestMetrics
82
+ if (!raspDuration && !raspDurationExt) return
83
+
84
+ const versionsTags = getVersionsTags(wafVersion, rulesVersion)
85
+
86
+ if (raspDuration) {
87
+ appsecMetrics.distribution('rasp.duration', versionsTags).track(raspDuration)
88
+ }
89
+
90
+ if (raspDurationExt) {
91
+ appsecMetrics.distribution('rasp.duration_ext', versionsTags).track(raspDurationExt)
92
+ }
93
+ }
94
+
80
95
  function trackRaspRuleSkipped (raspRule, reason) {
81
96
  const tags = { reason, rule_type: raspRule.type }
82
97
 
@@ -100,4 +115,5 @@ module.exports = {
100
115
  trackRaspMetrics,
101
116
  trackRaspRuleMatch,
102
117
  trackRaspRuleSkipped,
118
+ incrementRaspDurationMetrics,
103
119
  }
@@ -34,6 +34,14 @@ function addWafRequestMetrics (store, { duration, durationExt, wafTimeout, error
34
34
  function trackWafMetrics (store, metrics) {
35
35
  const versionsTags = getVersionsTags(metrics.wafVersion, metrics.rulesVersion)
36
36
 
37
+ const requestMetrics = store[DD_TELEMETRY_REQUEST_METRICS]
38
+ if (metrics.wafVersion) {
39
+ requestMetrics.wafVersion = metrics.wafVersion
40
+ }
41
+ if (metrics.rulesVersion) {
42
+ requestMetrics.rulesVersion = metrics.rulesVersion
43
+ }
44
+
37
45
  const metricTags = getOrCreateMetricTags(store, versionsTags)
38
46
 
39
47
  if (metrics.blockFailed) {
@@ -118,6 +126,21 @@ function incrementWafRequests (store) {
118
126
  }
119
127
  }
120
128
 
129
+ function incrementWafDurationMetrics (requestMetrics) {
130
+ const { duration, durationExt, wafVersion, rulesVersion } = requestMetrics
131
+ if (!duration && !durationExt) return
132
+
133
+ const versionsTags = getVersionsTags(wafVersion, rulesVersion)
134
+
135
+ if (duration) {
136
+ appsecMetrics.distribution('waf.duration', versionsTags).track(duration)
137
+ }
138
+
139
+ if (durationExt) {
140
+ appsecMetrics.distribution('waf.duration_ext', versionsTags).track(durationExt)
141
+ }
142
+ }
143
+
121
144
  function incrementTruncatedMetrics (metrics, truncationReason) {
122
145
  const truncationTags = { truncation_reason: truncationReason }
123
146
  appsecMetrics.count('waf.input_truncated', truncationTags).inc(1)
@@ -140,4 +163,5 @@ module.exports = {
140
163
  incrementWafUpdates,
141
164
  incrementWafConfigErrors,
142
165
  incrementWafRequests,
166
+ incrementWafDurationMetrics,
143
167
  }
@@ -9,6 +9,23 @@ const FIELD_HAS = 4
9
9
  const FIELD_DELETE = 8
10
10
  const FIELD_NAME_LENGTH = 16
11
11
 
12
+ const b3FlagsHeader = 'x-b3-flags'
13
+ const b3Header = 'b3'
14
+ const b3SampledHeader = 'x-b3-sampled'
15
+ const b3SpanIdHeader = 'x-b3-spanid'
16
+ const b3TraceIdHeader = 'x-b3-traceid'
17
+ const baggageHeader = 'baggage'
18
+ const datadogOriginHeader = 'x-datadog-origin'
19
+ const datadogParentIdHeader = 'x-datadog-parent-id'
20
+ const datadogSamplingPriorityHeader = 'x-datadog-sampling-priority'
21
+ const datadogTagsHeader = 'x-datadog-tags'
22
+ const datadogTraceIdHeader = 'x-datadog-trace-id'
23
+ const dsmBase64Header = 'dd-pathway-ctx-base64'
24
+ const dsmBinaryHeader = 'dd-pathway-ctx'
25
+ const sqsdHeader = 'x-aws-sqsd-attr-_datadog'
26
+ const traceparentHeader = 'traceparent'
27
+ const tracestateHeader = 'tracestate'
28
+
12
29
  /** @type {Record<string, unknown>} */
13
30
  const carrierOperations = {}
14
31
 
@@ -24,7 +41,7 @@ const carrierOperations = {}
24
41
  /**
25
42
  * @template Value
26
43
  * @typedef {object} CarrierField
27
- * @property {(carrier: Carrier) => Value | undefined} read
44
+ * @property {((carrier: Carrier) => Value | undefined) | undefined} read
28
45
  * @property {(carrier: Carrier, value: Value) => void} write
29
46
  * @property {(carrier: Carrier) => boolean} has
30
47
  * @property {(carrier: Carrier) => void} delete
@@ -53,19 +70,13 @@ function defineCapability (name, operations, capability) {
53
70
  * @template Value
54
71
  * @param {string} fieldName
55
72
  * @param {string} headerName
56
- * @param {(value: unknown) => Value | undefined} resolve
73
+ * @param {((carrier: Carrier) => Value | undefined) | undefined} read
57
74
  * @param {number} operations
58
75
  * @returns {CarrierField<Value>}
59
76
  */
60
- function defineField (fieldName, headerName, resolve, operations) {
77
+ function defineField (fieldName, headerName, read, operations) {
61
78
  return defineCapability(fieldName, operations, {
62
- /**
63
- * @param {Carrier} carrier
64
- * @returns {Value | undefined}
65
- */
66
- read (carrier) {
67
- return resolve(carrier[headerName])
68
- },
79
+ read,
69
80
 
70
81
  /**
71
82
  * @param {Carrier} carrier
@@ -183,48 +194,96 @@ function readLastPathway (value) {
183
194
  return result
184
195
  }
185
196
 
197
+ /** @param {Carrier} carrier */
198
+ function readB3Flags (carrier) { return readLastText(carrier[b3FlagsHeader]) }
199
+
200
+ /** @param {Carrier} carrier */
201
+ function readB3 (carrier) { return readLastText(carrier[b3Header]) }
202
+
203
+ /** @param {Carrier} carrier */
204
+ function readB3Sampled (carrier) { return readLastText(carrier[b3SampledHeader]) }
205
+
206
+ /** @param {Carrier} carrier */
207
+ function readB3SpanId (carrier) { return readLastText(carrier[b3SpanIdHeader]) }
208
+
209
+ /** @param {Carrier} carrier */
210
+ function readB3TraceId (carrier) { return readLastText(carrier[b3TraceIdHeader]) }
211
+
212
+ /** @param {Carrier} carrier */
213
+ function readBaggage (carrier) { return readListText(carrier[baggageHeader]) }
214
+
215
+ /** @param {Carrier} carrier */
216
+ function readDatadogOrigin (carrier) { return readLastText(carrier[datadogOriginHeader]) }
217
+
218
+ /** @param {Carrier} carrier */
219
+ function readDatadogParentId (carrier) { return readLastText(carrier[datadogParentIdHeader]) }
220
+
221
+ /** @param {Carrier} carrier */
222
+ function readDatadogSamplingPriority (carrier) { return readLastText(carrier[datadogSamplingPriorityHeader]) }
223
+
224
+ /** @param {Carrier} carrier */
225
+ function readDatadogTags (carrier) { return readListText(carrier[datadogTagsHeader]) }
226
+
227
+ /** @param {Carrier} carrier */
228
+ function readDatadogTraceId (carrier) { return readLastText(carrier[datadogTraceIdHeader]) }
229
+
230
+ /** @param {Carrier} carrier */
231
+ function readDsmBase64 (carrier) { return readLastPathway(carrier[dsmBase64Header]) }
232
+
233
+ /** @param {Carrier} carrier */
234
+ function readDsmBinary (carrier) { return readLastPathway(carrier[dsmBinaryHeader]) }
235
+
236
+ /** @param {Carrier} carrier */
237
+ function readSqsd (carrier) { return readLastText(carrier[sqsdHeader]) }
238
+
239
+ /** @param {Carrier} carrier */
240
+ function readTraceparent (carrier) { return readUniqueText(carrier[traceparentHeader]) }
241
+
242
+ /** @param {Carrier} carrier */
243
+ function readTracestate (carrier) { return readListText(carrier[tracestateHeader]) }
244
+
186
245
  const datadogTraceId = defineField(
187
246
  'datadogTraceId',
188
- 'x-datadog-trace-id',
189
- readLastText,
247
+ datadogTraceIdHeader,
248
+ readDatadogTraceId,
190
249
  FIELD_READ | FIELD_WRITE
191
250
  )
192
251
  const datadogParentId = defineField(
193
252
  'datadogParentId',
194
- 'x-datadog-parent-id',
195
- readLastText,
253
+ datadogParentIdHeader,
254
+ readDatadogParentId,
196
255
  FIELD_READ | FIELD_WRITE | FIELD_DELETE
197
256
  )
198
257
  const datadogOrigin = defineField(
199
258
  'datadogOrigin',
200
- 'x-datadog-origin',
201
- readLastText,
259
+ datadogOriginHeader,
260
+ readDatadogOrigin,
202
261
  FIELD_READ | FIELD_WRITE
203
262
  )
204
263
  const datadogSamplingPriority = defineField(
205
264
  'datadogSamplingPriority',
206
- 'x-datadog-sampling-priority',
207
- readLastText,
265
+ datadogSamplingPriorityHeader,
266
+ readDatadogSamplingPriority,
208
267
  FIELD_READ | FIELD_WRITE
209
268
  )
210
- defineField('datadogTags', 'x-datadog-tags', readListText, FIELD_READ | FIELD_WRITE)
211
- defineField('baggage', 'baggage', readListText, FIELD_READ | FIELD_WRITE)
212
- const b3TraceId = defineField('b3TraceId', 'x-b3-traceid', readLastText, FIELD_READ | FIELD_WRITE)
213
- const b3SpanId = defineField('b3SpanId', 'x-b3-spanid', readLastText, FIELD_READ | FIELD_WRITE)
214
- const b3ParentId = defineField('b3ParentId', 'x-b3-parentspanid', readLastText, FIELD_WRITE)
215
- const b3Sampled = defineField('b3Sampled', 'x-b3-sampled', readLastText, FIELD_READ | FIELD_WRITE)
216
- const b3Flags = defineField('b3Flags', 'x-b3-flags', readLastText, FIELD_READ | FIELD_WRITE)
217
- const b3 = defineField('b3', 'b3', readLastText, FIELD_READ | FIELD_WRITE)
218
- defineField('sqsd', 'x-aws-sqsd-attr-_datadog', readLastText, FIELD_READ)
219
- const traceparent = defineField('traceparent', 'traceparent', readUniqueText, FIELD_READ | FIELD_WRITE)
220
- const tracestate = defineField('tracestate', 'tracestate', readListText, FIELD_READ | FIELD_WRITE)
269
+ defineField('datadogTags', datadogTagsHeader, readDatadogTags, FIELD_READ | FIELD_WRITE)
270
+ defineField('baggage', baggageHeader, readBaggage, FIELD_READ | FIELD_WRITE)
271
+ const b3TraceId = defineField('b3TraceId', b3TraceIdHeader, readB3TraceId, FIELD_READ | FIELD_WRITE)
272
+ const b3SpanId = defineField('b3SpanId', b3SpanIdHeader, readB3SpanId, FIELD_READ | FIELD_WRITE)
273
+ const b3ParentId = defineField('b3ParentId', 'x-b3-parentspanid', undefined, FIELD_WRITE)
274
+ const b3Sampled = defineField('b3Sampled', b3SampledHeader, readB3Sampled, FIELD_READ | FIELD_WRITE)
275
+ const b3Flags = defineField('b3Flags', b3FlagsHeader, readB3Flags, FIELD_READ | FIELD_WRITE)
276
+ const b3 = defineField('b3', b3Header, readB3, FIELD_READ | FIELD_WRITE)
277
+ defineField('sqsd', sqsdHeader, readSqsd, FIELD_READ)
278
+ const traceparent = defineField('traceparent', traceparentHeader, readTraceparent, FIELD_READ | FIELD_WRITE)
279
+ const tracestate = defineField('tracestate', tracestateHeader, readTracestate, FIELD_READ | FIELD_WRITE)
221
280
  const dsmBase64 = defineField(
222
281
  'dsmBase64',
223
- 'dd-pathway-ctx-base64',
224
- readLastPathway,
282
+ dsmBase64Header,
283
+ readDsmBase64,
225
284
  FIELD_READ | FIELD_WRITE | FIELD_HAS | FIELD_NAME_LENGTH
226
285
  )
227
- const dsmBinary = defineField('dsmBinary', 'dd-pathway-ctx', readLastPathway, FIELD_READ | FIELD_HAS)
286
+ const dsmBinary = defineField('dsmBinary', dsmBinaryHeader, readDsmBinary, FIELD_READ | FIELD_HAS)
228
287
 
229
288
  const legacyBaggagePrefix = 'ot-baggage-'
230
289
  // RFC 7230 `token`, compatible with Node.js header-name validation.
@@ -314,23 +373,23 @@ function pickDsm (carrier) {
314
373
  * @property {typeof dsmBinary.has} hasDsmBinary
315
374
  * @property {typeof pickDsm} pickDsm
316
375
  * @property {typeof pickTextMap} pickTextMap
317
- * @property {(carrier: Carrier) => string | undefined} readBaggage
318
- * @property {typeof b3.read} readB3
319
- * @property {typeof b3Flags.read} readB3Flags
320
- * @property {typeof b3Sampled.read} readB3Sampled
321
- * @property {typeof b3SpanId.read} readB3SpanId
322
- * @property {typeof b3TraceId.read} readB3TraceId
323
- * @property {typeof datadogOrigin.read} readDatadogOrigin
324
- * @property {typeof datadogParentId.read} readDatadogParentId
325
- * @property {typeof datadogSamplingPriority.read} readDatadogSamplingPriority
326
- * @property {(carrier: Carrier) => string | undefined} readDatadogTags
327
- * @property {typeof datadogTraceId.read} readDatadogTraceId
328
- * @property {typeof dsmBase64.read} readDsmBase64
329
- * @property {typeof dsmBinary.read} readDsmBinary
376
+ * @property {typeof readBaggage} readBaggage
377
+ * @property {typeof readB3} readB3
378
+ * @property {typeof readB3Flags} readB3Flags
379
+ * @property {typeof readB3Sampled} readB3Sampled
380
+ * @property {typeof readB3SpanId} readB3SpanId
381
+ * @property {typeof readB3TraceId} readB3TraceId
382
+ * @property {typeof readDatadogOrigin} readDatadogOrigin
383
+ * @property {typeof readDatadogParentId} readDatadogParentId
384
+ * @property {typeof readDatadogSamplingPriority} readDatadogSamplingPriority
385
+ * @property {typeof readDatadogTags} readDatadogTags
386
+ * @property {typeof readDatadogTraceId} readDatadogTraceId
387
+ * @property {typeof readDsmBase64} readDsmBase64
388
+ * @property {typeof readDsmBinary} readDsmBinary
330
389
  * @property {(carrier: Carrier, target: Record<string, string>) => void} readLegacyBaggage
331
- * @property {(carrier: Carrier) => string | undefined} readSqsd
332
- * @property {typeof traceparent.read} readTraceparent
333
- * @property {typeof tracestate.read} readTracestate
390
+ * @property {typeof readSqsd} readSqsd
391
+ * @property {typeof readTraceparent} readTraceparent
392
+ * @property {typeof readTracestate} readTracestate
334
393
  * @property {(carrier: Carrier, value: string) => void} writeBaggage
335
394
  * @property {typeof b3.write} writeB3
336
395
  * @property {typeof b3Flags.write} writeB3Flags