dd-trace 6.12.0 → 6.14.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 (251) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  8. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  9. package/ci/test-optimization-validation/package-check.js +1 -1
  10. package/ci/test-optimization-validation/report-writer.js +7 -5
  11. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  12. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  13. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  14. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  15. package/index.d.ts +127 -26
  16. package/package.json +22 -19
  17. package/packages/datadog-esbuild/index.js +3 -1
  18. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  19. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  20. package/packages/datadog-instrumentations/src/fs.js +24 -20
  21. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  22. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  23. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  24. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  25. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  26. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +266 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  34. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  35. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +279 -0
  36. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +12 -1
  37. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  38. package/packages/datadog-instrumentations/src/jest.js +165 -38
  39. package/packages/datadog-instrumentations/src/knex.js +76 -0
  40. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  41. package/packages/datadog-instrumentations/src/mariadb.js +13 -0
  42. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  43. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  44. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  45. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  46. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  47. package/packages/datadog-instrumentations/src/net.js +14 -17
  48. package/packages/datadog-instrumentations/src/next.js +212 -62
  49. package/packages/datadog-instrumentations/src/openai.js +42 -40
  50. package/packages/datadog-instrumentations/src/pg.js +15 -0
  51. package/packages/datadog-instrumentations/src/pino.js +15 -4
  52. package/packages/datadog-instrumentations/src/playwright.js +137 -27
  53. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  54. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  55. package/packages/datadog-instrumentations/src/undici.js +53 -4
  56. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +44 -7
  57. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  58. package/packages/datadog-instrumentations/src/vitest-worker.js +82 -14
  59. package/packages/datadog-instrumentations/src/webdriverio.js +986 -1
  60. package/packages/datadog-instrumentations/src/winston.js +6 -3
  61. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  62. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  63. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  64. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  65. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  66. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  67. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  68. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  69. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  70. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  71. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  72. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  73. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  74. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  75. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  76. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  77. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  78. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  79. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  80. package/packages/datadog-plugin-http/src/client.js +2 -15
  81. package/packages/datadog-plugin-http2/src/client.js +2 -15
  82. package/packages/datadog-plugin-jest/src/index.js +0 -6
  83. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  84. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  85. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  86. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  87. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  88. package/packages/datadog-plugin-next/src/index.js +63 -29
  89. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  90. package/packages/datadog-plugin-playwright/src/index.js +176 -28
  91. package/packages/datadog-plugin-undici/src/index.js +29 -56
  92. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  93. package/packages/dd-trace/src/aiguard/evaluation.js +96 -21
  94. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  95. package/packages/dd-trace/src/aiguard/messages/anthropic.js +29 -10
  96. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  97. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  98. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  99. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  100. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  101. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  102. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  103. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  104. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  107. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  108. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  109. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  110. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  111. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  112. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  113. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  114. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  115. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  116. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  117. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +7 -150
  118. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  119. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +397 -234
  122. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  124. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  125. package/packages/dd-trace/src/ci-visibility/final-flush.js +21 -0
  126. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  127. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  128. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  129. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  130. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  131. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  132. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  133. package/packages/dd-trace/src/config/index.js +75 -14
  134. package/packages/dd-trace/src/config/parsers.js +11 -1
  135. package/packages/dd-trace/src/config/stable.js +3 -3
  136. package/packages/dd-trace/src/config/supported-configurations.json +47 -2
  137. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  138. package/packages/dd-trace/src/debugger/config.js +12 -1
  139. package/packages/dd-trace/src/debugger/constants.js +1 -0
  140. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  141. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  142. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -39
  143. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  144. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  145. package/packages/dd-trace/src/debugger/index.js +23 -3
  146. package/packages/dd-trace/src/dogstatsd.js +532 -71
  147. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  148. package/packages/dd-trace/src/encode/0.4.js +5 -5
  149. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  150. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  151. package/packages/dd-trace/src/exporters/agent/index.js +24 -3
  152. package/packages/dd-trace/src/exporters/agent/writer.js +28 -11
  153. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  154. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  155. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  156. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  157. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  158. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  159. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  160. package/packages/dd-trace/src/exporters/common/writer.js +31 -7
  161. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  162. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  163. package/packages/dd-trace/src/flush.js +105 -0
  164. package/packages/dd-trace/src/id.js +14 -0
  165. package/packages/dd-trace/src/index.js +5 -2
  166. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  167. package/packages/dd-trace/src/llmobs/experiments/dataset.js +52 -2
  168. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  169. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  170. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  171. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  172. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  173. package/packages/dd-trace/src/llmobs/index.js +111 -48
  174. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  175. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  176. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  177. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  178. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  180. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  181. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  182. package/packages/dd-trace/src/llmobs/util.js +0 -33
  183. package/packages/dd-trace/src/llmobs/writers/base.js +106 -53
  184. package/packages/dd-trace/src/log/index.js +7 -1
  185. package/packages/dd-trace/src/log-submission/log-submission-plugin.js +249 -0
  186. package/packages/dd-trace/src/noop/span.js +5 -0
  187. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  188. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  189. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  190. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  191. package/packages/dd-trace/src/opentelemetry/logs/index.js +9 -25
  192. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  193. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  194. package/packages/dd-trace/src/opentelemetry/metrics/index.js +18 -23
  195. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  196. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +18 -6
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +14 -2
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  199. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  200. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  201. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  202. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  203. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  204. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  205. package/packages/dd-trace/src/opentracing/propagation/text_map.js +494 -351
  206. package/packages/dd-trace/src/opentracing/span.js +28 -0
  207. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  208. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  209. package/packages/dd-trace/src/plugin_manager.js +2 -0
  210. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  211. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  212. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  213. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  214. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  215. package/packages/dd-trace/src/plugins/util/test.js +157 -41
  216. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  217. package/packages/dd-trace/src/plugins/util/web.js +6 -74
  218. package/packages/dd-trace/src/profiling/config.js +1 -0
  219. package/packages/dd-trace/src/profiling/oom.js +11 -5
  220. package/packages/dd-trace/src/profiling/profiler.js +30 -13
  221. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  222. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  223. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  224. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  225. package/packages/dd-trace/src/proxy.js +108 -10
  226. package/packages/dd-trace/src/remote_config/index.js +319 -101
  227. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  228. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  229. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  230. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +10 -1
  231. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +22 -7
  232. package/packages/dd-trace/src/sampling_rule.js +36 -9
  233. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +51 -0
  234. package/packages/dd-trace/src/serverless/vercel.js +155 -0
  235. package/packages/dd-trace/src/serverless.js +45 -21
  236. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  237. package/packages/dd-trace/src/span_stats.js +64 -31
  238. package/packages/dd-trace/src/startup-log.js +3 -2
  239. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  240. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  241. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  242. package/packages/dd-trace/src/tracer.js +26 -5
  243. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  244. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  245. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  246. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  247. package/vendor/dist/protobufjs/index.js +1 -1
  248. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  249. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  250. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +0 -53
  251. package/packages/dd-trace/src/encode/agentless-json.js +0 -209
@@ -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
  }
@@ -5,6 +5,8 @@ const { isSchemaAttribute } = require('../reporter')
5
5
  const appsecTelemetry = require('../telemetry')
6
6
  const sampler = require('./sampler')
7
7
 
8
+ /** @typedef {import('../../opentracing/span')} DatadogSpan */
9
+
8
10
  /**
9
11
  * Map a sampling decision into the corresponding API Security telemetry metric.
10
12
  *
@@ -13,14 +15,14 @@ const sampler = require('./sampler')
13
15
  * - MISSING_ROUTE: missing_route
14
16
  * - SKIP: no metric emitted
15
17
  *
16
- * @param {import('http').IncomingMessage} req
18
+ * @param {DatadogSpan} rootSpan Span the sampling decision was attached to
17
19
  * @param {'sample' | 'missing_route' | 'skip'} samplingDecision Sampler decision
18
20
  * @param {{ attributes?: Record<string, unknown> } | undefined} wafResult WAF run result
19
21
  */
20
- function reportRequest (req, samplingDecision, wafResult) {
22
+ function reportRootSpanRequest (rootSpan, samplingDecision, wafResult) {
21
23
  switch (samplingDecision) {
22
24
  case sampler.SamplingDecision.SAMPLE: {
23
- const framework = getFramework(req)
25
+ const framework = getFramework(rootSpan)
24
26
  if (hasSchemaAttributes(wafResult?.attributes)) {
25
27
  appsecTelemetry.incrementApiSecRequestSchemaMetric(framework)
26
28
  } else {
@@ -29,13 +31,26 @@ function reportRequest (req, samplingDecision, wafResult) {
29
31
  break
30
32
  }
31
33
  case sampler.SamplingDecision.MISSING_ROUTE:
32
- appsecTelemetry.incrementApiSecMissingRouteMetric(getFramework(req))
34
+ appsecTelemetry.incrementApiSecMissingRouteMetric(getFramework(rootSpan))
33
35
  break
34
36
  }
35
37
  }
36
38
 
37
- function getFramework (req) {
38
- return web.root(req)?.context()?.getTag?.('component')
39
+ /**
40
+ * Node HTTP adapter over {@link reportRootSpanRequest}.
41
+ *
42
+ * @param {import('http').IncomingMessage} req
43
+ * @param {'sample' | 'missing_route' | 'skip'} samplingDecision Sampler decision
44
+ * @param {{ attributes?: Record<string, unknown> } | undefined} wafResult WAF run result
45
+ */
46
+ function reportRequest (req, samplingDecision, wafResult) {
47
+ if (samplingDecision === sampler.SamplingDecision.SKIP) return
48
+
49
+ reportRootSpanRequest(web.root(req), samplingDecision, wafResult)
50
+ }
51
+
52
+ function getFramework (rootSpan) {
53
+ return rootSpan?.context?.()?.getTag?.('component')
39
54
  }
40
55
 
41
56
  function hasSchemaAttributes (attributes) {
@@ -50,6 +65,8 @@ module.exports = {
50
65
  configure: sampler.configure,
51
66
  disable: sampler.disable,
52
67
  sampleRequest: sampler.sampleRequest,
68
+ sampleRootSpanRequest: sampler.sampleRootSpanRequest,
53
69
  reportRequest,
70
+ reportRootSpanRequest,
54
71
  SamplingDecision: sampler.SamplingDecision,
55
72
  }
@@ -8,6 +8,8 @@ const { keepTrace } = require('../../priority_sampler')
8
8
  const { ASM } = require('../../standalone/product')
9
9
  const { isBlocked } = require('../blocking')
10
10
 
11
+ /** @typedef {import('../../opentracing/span')} DatadogSpan */
12
+
11
13
  const MAX_SIZE = 4096
12
14
 
13
15
  const SamplingDecision = Object.freeze({
@@ -44,77 +46,113 @@ function disable () {
44
46
  }
45
47
 
46
48
  /**
47
- * @param {import('http').IncomingMessage} req
48
- * @param {import('http').ServerResponse} res
49
+ * @param {DatadogSpan} rootSpan Span the sampling decision is attached to
50
+ * @param {object} request
51
+ * @param {string} request.method
52
+ * @param {number|string} request.statusCode
53
+ * @param {string|undefined} request.route Tri-state: a route string, an empty string (still a
54
+ * valid route — dd-trace-js represents the express root path '/' as an empty path segment), or
55
+ * `undefined` meaning no route information at all.
56
+ * @param {boolean} [request.blocked] Whether the response was blocked by ASM
49
57
  * @param {boolean} record When true and the decision is SAMPLE, records the endpoint in the TTL cache
50
58
  * @returns {'sample' | 'missing_route' | 'skip'}
51
59
  */
52
- function sampleRequest (req, res, record = false) {
60
+ function sampleRootSpanRequest (rootSpan, request, record = false) {
53
61
  if (!enabled) return SamplingDecision.SKIP
54
62
 
55
- const rootSpan = web.root(req)
56
63
  if (!rootSpan) return SamplingDecision.SKIP
57
64
 
58
- if (!asmStandaloneEnabled) {
59
- let priority = getSpanPriority(rootSpan)
60
- if (!priority) {
61
- rootSpan._prioritySampler?.sample(rootSpan)
62
- priority = getSpanPriority(rootSpan)
63
- }
65
+ const { method, statusCode, route, blocked = false } = request ?? {}
64
66
 
65
- if (priority === AUTO_REJECT || priority === USER_REJECT) {
66
- return SamplingDecision.SKIP
67
- }
67
+ if (!method || !statusCode) {
68
+ log.warn('[ASM] Unsupported groupkey for API security')
69
+ return SamplingDecision.SKIP
68
70
  }
69
71
 
70
- const resolved = resolveSamplingKey(req, res)
71
- if (!resolved) return SamplingDecision.SKIP
72
+ if (isRejected(rootSpan)) return SamplingDecision.SKIP
72
73
 
73
- if (record && resolved.route === null) {
74
- if (resolved.status === 404 || isBlocked(res)) return SamplingDecision.SKIP
74
+ if (isRoutelessRecord(route, record)) {
75
+ if (isNotFound(statusCode) || blocked) return SamplingDecision.SKIP
75
76
  return SamplingDecision.MISSING_ROUTE
76
77
  }
77
78
 
78
- if (sampledRequests.has(resolved.key)) return SamplingDecision.SKIP
79
+ const key = buildSamplingKey(method, route, statusCode)
80
+ if (sampledRequests.has(key)) return SamplingDecision.SKIP
79
81
 
80
82
  if (asmStandaloneEnabled) {
81
83
  keepTrace(rootSpan, ASM)
82
84
  }
83
85
 
84
86
  if (record) {
85
- sampledRequests.set(resolved.key, undefined)
87
+ sampledRequests.set(key, undefined)
86
88
  }
87
89
 
88
90
  return SamplingDecision.SAMPLE
89
91
  }
90
92
 
91
93
  /**
94
+ * Node HTTP adapter over {@link sampleRootSpanRequest}.
95
+ *
92
96
  * @param {import('http').IncomingMessage} req
93
97
  * @param {import('http').ServerResponse} res
94
- * @returns {boolean} Whether this request's endpoint is currently recorded in the TTL cache.
98
+ * @param {boolean} record When true and the decision is SAMPLE, records the endpoint in the TTL cache
99
+ * @returns {'sample' | 'missing_route' | 'skip'}
95
100
  */
96
- function wasSampled (req, res) {
97
- const resolved = resolveSamplingKey(req, res)
98
- return resolved !== null && sampledRequests.has(resolved.key)
99
- }
101
+ function sampleRequest (req, res, record = false) {
102
+ if (!enabled) return SamplingDecision.SKIP
100
103
 
101
- function resolveSamplingKey (req, res) {
102
- const method = req.method
103
- const status = res.statusCode
104
+ const rootSpan = web.root(req)
105
+ if (!rootSpan) return SamplingDecision.SKIP
104
106
 
105
- if (!method || !status) {
106
- log.warn('[ASM] Unsupported groupkey for API security')
107
- return null
108
- }
107
+ if (isRejected(rootSpan)) return SamplingDecision.SKIP
108
+
109
+ const statusCode = res.statusCode
110
+ const route = getRouteOrEndpoint(web.getContext(req), statusCode)
111
+
112
+ return sampleRootSpanRequest(rootSpan, {
113
+ method: req.method,
114
+ statusCode,
115
+ route,
116
+ blocked: isRoutelessRecord(route, record) && !isNotFound(statusCode) ? isBlocked(res) : false,
117
+ }, record)
118
+ }
109
119
 
110
- const context = web.getContext(req)
111
- const route = getRouteOrEndpoint(context, status)
120
+ /**
121
+ * Whether this is a request with no route information
122
+ *
123
+ * @param {string|undefined} route
124
+ * @param {boolean} record
125
+ * @returns {boolean}
126
+ */
127
+ function isRoutelessRecord (route, record) {
128
+ return record && route === undefined
129
+ }
112
130
 
113
- // route === null signals "no route information at all". An empty string is still a valid
114
- // route (dd-trace-js represents the express root path '/' as an empty path segment).
115
- return { method, status, route, key: method + (route ?? '') + status }
131
+ /**
132
+ * @param {number|string} statusCode
133
+ * @returns {boolean}
134
+ */
135
+ function isNotFound (statusCode) {
136
+ return Number(statusCode) === 404
137
+ }
138
+
139
+ /**
140
+ * @param {string} method
141
+ * @param {string|undefined} route A route string, an empty string (still a valid route), or
142
+ * `undefined`
143
+ * @param {number|string} statusCode
144
+ * @returns {string}
145
+ */
146
+ function buildSamplingKey (method, route, statusCode) {
147
+ return method + (route ?? '') + statusCode
116
148
  }
117
149
 
150
+ /**
151
+ * @param {{ paths?: string[], span?: object }} [context] Web context of the request
152
+ * @param {number|string} statusCode
153
+ * @returns {string|undefined} A route string, an empty string (still a valid route), or
154
+ * `undefined` when no route information is available.
155
+ */
118
156
  function getRouteOrEndpoint (context, statusCode) {
119
157
  // The router plugin populates `context.paths` whenever the framework matched something.
120
158
  // For express's root '/' route the matched path is normalized to '' (see datadog-plugin-router),
@@ -125,23 +163,44 @@ function getRouteOrEndpoint (context, statusCode) {
125
163
  return paths.join('')
126
164
  }
127
165
 
128
- if (statusCode === 404) return null
166
+ if (isNotFound(statusCode)) return
129
167
 
130
168
  const endpoint = context?.span?.context()?.getTag?.('http.endpoint')
131
169
  if (endpoint) return endpoint
170
+ }
132
171
 
133
- return null
172
+ /**
173
+ * Whether the span's trace is dropped, forcing the sampling decision if none was made yet.
174
+ *
175
+ * Always false under ASM standalone: there the trace is kept via `keepTrace(rootSpan, ASM)`
176
+ * regardless of the APM sampling decision.
177
+ *
178
+ * @param {DatadogSpan} rootSpan
179
+ * @returns {boolean}
180
+ */
181
+ function isRejected (rootSpan) {
182
+ if (asmStandaloneEnabled) return false
183
+
184
+ let priority = getSpanPriority(rootSpan)
185
+ if (priority == null) {
186
+ rootSpan._prioritySampler?.sample(rootSpan)
187
+ priority = getSpanPriority(rootSpan)
188
+ }
189
+
190
+ return priority === AUTO_REJECT || priority === USER_REJECT
134
191
  }
135
192
 
136
193
  function getSpanPriority (span) {
137
194
  const spanContext = span.context?.()
138
- return spanContext._sampling?.priority
195
+ const priority = spanContext?._sampling?.priority
196
+
197
+ return priority == null ? priority : Number(priority)
139
198
  }
140
199
 
141
200
  module.exports = {
142
201
  configure,
143
202
  disable,
144
203
  sampleRequest,
145
- wasSampled,
204
+ sampleRootSpanRequest,
146
205
  SamplingDecision,
147
206
  }
@@ -32,11 +32,15 @@ function disable () {
32
32
  disableGraphql()
33
33
  }
34
34
 
35
- function onGraphqlStartResolver ({ abortController, resolverInfo }) {
35
+ /**
36
+ * @param {{ createResolverInfo?: (data: object) => object | null }} data
37
+ */
38
+ function onGraphqlStartResolver (data) {
36
39
  const req = getActiveRequest()
37
40
 
38
41
  if (!req) return
39
42
 
43
+ const resolverInfo = data.createResolverInfo?.(data)
40
44
  if (!resolverInfo || typeof resolverInfo !== 'object') return
41
45
 
42
46
  const result = waf.run({ ephemeral: { [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo } }, req)
@@ -46,7 +50,7 @@ function onGraphqlStartResolver ({ abortController, resolverInfo }) {
46
50
  if (requestData?.isInGraphqlRequest) {
47
51
  requestData.blocked = true
48
52
  requestData.wafAction = blockingAction
49
- abortController?.abort()
53
+ data.abortController?.abort()
50
54
  }
51
55
  }
52
56
  }
@@ -178,12 +178,12 @@ class TaintTrackingPlugin extends SourceIastPlugin {
178
178
  addGraphQLSubscriptions () {
179
179
  this.addSub(
180
180
  { channelName: 'apm:graphql:resolve:start', tag: HTTP_REQUEST_BODY },
181
- (data) => {
181
+ ({ rootCtx, args }) => {
182
182
  const iastContext = getIastContext(storage('legacy').getStore())
183
- const source = data.rootCtx?.source
183
+ const source = rootCtx?.source
184
184
  const ranges = source && getRanges(iastContext, source)
185
185
  if (ranges?.length) {
186
- this._taintTrackingHandler(ranges[0].iinfo.type, data.args, null, iastContext)
186
+ this._taintTrackingHandler(ranges[0].iinfo.type, args, null, iastContext)
187
187
  }
188
188
  }
189
189
  )
@@ -242,7 +242,8 @@ function lodashTaintTrackingHandler (message) {
242
242
  const context = getContextDefault()
243
243
  const transactionId = getTransactionId(context)
244
244
  if (transactionId) {
245
- message.result = getLodashTaintedUtilFn(message.operation)(transactionId, message.result, ...message.arguments)
245
+ const taintResult = getLodashTaintedUtilFn(message.operation)
246
+ message.result = taintResult(transactionId, message.result, ...message.arguments)
246
247
  }
247
248
  } catch (e) {
248
249
  log.error('[ASM] Error invoking CSI lodash %s', message.operation, e)
@@ -46,8 +46,14 @@ function filterTags (tags) {
46
46
  }
47
47
 
48
48
  function processTagValue (tags) {
49
- return tags.map(tag => tag.includes(':') ? getSegment(tag, ':', 1) : tag)
50
- .join('_').replaceAll('.', '_')
49
+ let processedTags = ''
50
+ let isFirstTag = true
51
+ for (const tag of tags) {
52
+ if (!isFirstTag) processedTags += '_'
53
+ processedTags += tag.includes(':') ? getSegment(tag, ':', 1) : tag
54
+ isFirstTag = false
55
+ }
56
+ return processedTags.replaceAll('.', '_')
51
57
  }
52
58
 
53
59
  module.exports = {
@@ -8,58 +8,33 @@ const MYSQL_STRING_LITERAL = String.raw`"(?:\\"|[^"])*"|'(?:\\'|[^'])*'`
8
8
  const LINE_COMMENT = '--.*$'
9
9
  const BLOCK_COMMENT = String.raw`/\*[\s\S]*\*/`
10
10
  const EXPONENT = String.raw`(?:E[-+]?\d+[fd]?)?`
11
- const INTEGER_NUMBER = String.raw`(?<!\w)\d+`
11
+ const INTEGER_NUMBER = String.raw`\b\d+`
12
12
  const DECIMAL_NUMBER = String.raw`\d*\.\d+`
13
13
  const HEX_NUMBER = 'x\'[0-9a-f]+\'|0x[0-9a-f]+'
14
14
  const BIN_NUMBER = 'b\'[0-9a-f]+\'|0b[0-9a-f]+'
15
15
  const NUMERIC_LITERAL =
16
- `[-+]?(?:${
17
- [
18
- HEX_NUMBER,
19
- BIN_NUMBER,
20
- DECIMAL_NUMBER + EXPONENT,
21
- INTEGER_NUMBER + EXPONENT,
22
- ].join('|')
23
- })`
16
+ `[-+]?(?:${HEX_NUMBER}|${BIN_NUMBER}|${DECIMAL_NUMBER + EXPONENT}|${INTEGER_NUMBER + EXPONENT})`
24
17
  const ORACLE_ESCAPED_LITERAL = String.raw`q'<.*?>'|q'\(.*?\)'|q'\{.*?\}'|q'\[.*?\]'|q'(?<ESCAPE>.).*?\k<ESCAPE>'`
25
18
 
26
19
  const patterns = {
27
20
  ANSI: new RegExp( // Default
28
- [
29
- NUMERIC_LITERAL,
30
- STRING_LITERAL,
31
- LINE_COMMENT,
32
- BLOCK_COMMENT,
33
- ].join('|'),
21
+ `${NUMERIC_LITERAL}|${STRING_LITERAL}|${LINE_COMMENT}|${BLOCK_COMMENT}`,
34
22
  'gmi'
35
23
  ),
36
24
  MYSQL: new RegExp(
37
- [
38
- NUMERIC_LITERAL,
39
- MYSQL_STRING_LITERAL,
40
- LINE_COMMENT,
41
- BLOCK_COMMENT,
42
- ].join('|'),
25
+ `${NUMERIC_LITERAL}|${MYSQL_STRING_LITERAL}|${LINE_COMMENT}|${BLOCK_COMMENT}`,
43
26
  'gmi'
44
27
  ),
45
28
  POSTGRES: new RegExp(
46
- [
47
- NUMERIC_LITERAL,
48
- POSTGRESQL_ESCAPED_LITERAL,
49
- STRING_LITERAL,
50
- LINE_COMMENT,
51
- BLOCK_COMMENT,
52
- ].join('|'),
29
+ `${NUMERIC_LITERAL}|${POSTGRESQL_ESCAPED_LITERAL}|${STRING_LITERAL}|${LINE_COMMENT}|${BLOCK_COMMENT}`,
30
+ 'gmi'
31
+ ),
32
+ ORACLE: new RegExp(
33
+ // The capture owns the quote delimiter while the lazy quantifier owns the body.
34
+ // eslint-disable-next-line regexp/optimal-quantifier-concatenation
35
+ `${NUMERIC_LITERAL}|${ORACLE_ESCAPED_LITERAL}|${STRING_LITERAL}|${LINE_COMMENT}|${BLOCK_COMMENT}`,
53
36
  'gmi'
54
37
  ),
55
- ORACLE: new RegExp([
56
- NUMERIC_LITERAL,
57
- ORACLE_ESCAPED_LITERAL,
58
- STRING_LITERAL,
59
- LINE_COMMENT,
60
- BLOCK_COMMENT,
61
- ].join('|'),
62
- 'gmi'),
63
38
  }
64
39
  patterns.SQLITE = patterns.MYSQL
65
40
  patterns.MARIADB = patterns.MYSQL
@@ -8,7 +8,7 @@ const AUTHORITY = '^(?:[^:]+:)?//([^@]+)@'
8
8
  // boundaries), so excluding them preserves match semantics for valid URLs while keeping
9
9
  // the regex linear on arbitrary input.
10
10
  const QUERY_FRAGMENT = '[?#&]([^=&;?#]+)=([^?#&]+)'
11
- const pattern = new RegExp([AUTHORITY, QUERY_FRAGMENT].join('|'), 'gmi')
11
+ const pattern = new RegExp(`${AUTHORITY}|${QUERY_FRAGMENT}`, 'gm')
12
12
 
13
13
  module.exports = function extractSensitiveRanges (evidence) {
14
14
  try {