dd-trace 6.21.0 → 6.22.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 (204) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  16. package/index.d.ts +25 -14
  17. package/initialize.mjs +1 -3
  18. package/openfeature/rules-based.d.ts +11 -0
  19. package/openfeature/rules-based.js +7 -0
  20. package/package.json +12 -10
  21. package/packages/datadog-core/src/storage.js +16 -0
  22. package/packages/datadog-esbuild/index.js +18 -12
  23. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  24. package/packages/datadog-instrumentations/src/ai.js +3 -3
  25. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  26. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  27. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  28. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  29. package/packages/datadog-instrumentations/src/cucumber.js +48 -33
  30. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  31. package/packages/datadog-instrumentations/src/electron.js +6 -6
  32. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  33. package/packages/datadog-instrumentations/src/fs.js +2 -2
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  35. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  36. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
  37. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  38. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  39. package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
  40. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  41. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
  42. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +7 -7
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  47. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  48. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  49. package/packages/datadog-instrumentations/src/jest.js +3 -10
  50. package/packages/datadog-instrumentations/src/knex.js +9 -5
  51. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  52. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  53. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  54. package/packages/datadog-instrumentations/src/mocha/utils.js +3 -2
  55. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -2
  56. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  57. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  58. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  59. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  60. package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
  61. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  62. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  63. package/packages/datadog-instrumentations/src/openai.js +7 -0
  64. package/packages/datadog-instrumentations/src/pino.js +5 -6
  65. package/packages/datadog-instrumentations/src/redis.js +27 -13
  66. package/packages/datadog-instrumentations/src/router.js +30 -0
  67. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  68. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
  69. package/packages/datadog-instrumentations/src/vitest-main.js +9 -15
  70. package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
  71. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  72. package/packages/datadog-instrumentations/src/vitest.js +2 -2
  73. package/packages/datadog-instrumentations/src/webdriverio.js +5 -8
  74. package/packages/datadog-instrumentations/src/ws.js +1 -4
  75. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  76. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  77. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  78. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  79. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  80. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  81. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  82. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  83. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  84. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  85. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  86. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -5
  87. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  88. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  89. package/packages/datadog-plugin-cypress/src/support.js +1 -4
  90. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  91. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  92. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  93. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  94. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  95. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  96. package/packages/datadog-plugin-mocha/src/index.js +7 -10
  97. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +8 -4
  98. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  99. package/packages/datadog-plugin-next/src/index.js +1 -2
  100. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  101. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  102. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  103. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  104. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  105. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  106. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  107. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  108. package/packages/datadog-webpack/index.js +55 -43
  109. package/packages/datadog-webpack/src/loader.js +56 -8
  110. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  111. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  112. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  113. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  114. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  115. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  116. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  117. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  118. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  119. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  120. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  121. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  122. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  123. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  124. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  125. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  126. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  127. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  128. package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
  129. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  130. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  131. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  132. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  133. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  134. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  135. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  136. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  137. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  138. package/packages/dd-trace/src/config/defaults.js +5 -5
  139. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
  140. package/packages/dd-trace/src/config/helper.js +34 -3
  141. package/packages/dd-trace/src/config/index.js +10 -9
  142. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  143. package/packages/dd-trace/src/config/parsers.js +2 -2
  144. package/packages/dd-trace/src/config/supported-configurations.json +35 -0
  145. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  146. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  147. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -0
  148. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  149. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  150. package/packages/dd-trace/src/dogstatsd.js +1 -1
  151. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  152. package/packages/dd-trace/src/exporters/common/request.js +1 -5
  153. package/packages/dd-trace/src/id.js +14 -1
  154. package/packages/dd-trace/src/lambda/handler.js +179 -19
  155. package/packages/dd-trace/src/lambda/index.js +15 -1
  156. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  157. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  158. package/packages/dd-trace/src/llmobs/constants/tags.js +12 -0
  159. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  160. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  161. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  162. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  163. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  164. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  165. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
  166. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  167. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  168. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  169. package/packages/dd-trace/src/llmobs/span_processor.js +1 -2
  170. package/packages/dd-trace/src/llmobs/tagger.js +2 -2
  171. package/packages/dd-trace/src/llmobs/util.js +1 -2
  172. package/packages/dd-trace/src/llmobs/writers/base.js +4 -5
  173. package/packages/dd-trace/src/log/log.js +1 -5
  174. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  175. package/packages/dd-trace/src/openfeature/flagging_provider.js +48 -21
  176. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  177. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  178. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  179. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  180. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
  181. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  182. package/packages/dd-trace/src/otel-sampling.js +4 -5
  183. package/packages/dd-trace/src/otel-thread-ctx.js +79 -13
  184. package/packages/dd-trace/src/plugins/database.js +7 -16
  185. package/packages/dd-trace/src/plugins/util/git.js +9 -25
  186. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  187. package/packages/dd-trace/src/plugins/util/test.js +48 -4
  188. package/packages/dd-trace/src/priority_sampler.js +1 -2
  189. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  190. package/packages/dd-trace/src/proxy.js +1 -2
  191. package/packages/dd-trace/src/ritm.js +4 -2
  192. package/packages/dd-trace/src/serverless.js +5 -0
  193. package/packages/dd-trace/src/span_processor.js +13 -17
  194. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  195. package/packages/dd-trace/src/util.js +29 -0
  196. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  197. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  198. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  199. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  200. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  201. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  202. package/vendor/dist/protobufjs/index.js +1 -1
  203. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  204. package/vendor/dist/shell-quote/index.js +1 -1
@@ -314,11 +314,11 @@ function assertMethod (target, name, method) {
314
314
  let message = 'No target object provided'
315
315
 
316
316
  if (target) {
317
- if (typeof target !== 'object' && typeof target !== 'function') {
318
- message = 'Invalid target'
319
- } else {
317
+ if (typeof target === 'object' || typeof target === 'function') {
320
318
  name = String(name)
321
319
  message = method ? `Original method ${name} is not a function` : `No original method ${name}`
320
+ } else {
321
+ message = 'Invalid target'
322
322
  }
323
323
  }
324
324
 
@@ -335,7 +335,8 @@ function assertMethod (target, name, method) {
335
335
  function assertNotClass (target) {
336
336
  // Frozen functions also have a non-writable `prototype`; inspect their source only then.
337
337
  if ('prototype' in target &&
338
- Object.getOwnPropertyDescriptor(target, 'prototype').writable === false &&
338
+ // Bound generators inherit `prototype` without owning one, so the descriptor may still be missing.
339
+ Object.getOwnPropertyDescriptor(target, 'prototype')?.writable === false &&
339
340
  (!Object.isFrozen(target) || Function.prototype.toString.call(target).startsWith('class'))) {
340
341
  throw new TypeError('Target is a native class constructor and cannot be wrapped.')
341
342
  }
@@ -6,11 +6,15 @@ const fs = require('node:fs')
6
6
  const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
7
7
  const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
8
8
  const hooks = require('../datadog-instrumentations/src/helpers/hooks')
9
+ const { getRewriteTarget } = require('../datadog-instrumentations/src/helpers/rewriter/targets')
9
10
  const { isESMFile } = require('../datadog-esbuild/src/utils')
11
+ const { getValueFromEnvSources } = require('../dd-trace/src/config/helper')
10
12
  const log = require('./src/log')
11
13
 
12
14
  const PLUGIN_NAME = 'DatadogWebpackPlugin'
13
15
 
16
+ const DD_BUNDLER_INJECT_GIT_METADATA = getValueFromEnvSources('DD_BUNDLER_INJECT_GIT_METADATA') !== false
17
+
14
18
  for (const hook of Object.values(hooks)) {
15
19
  if (hook !== null && typeof hook === 'object') {
16
20
  hook.fn()
@@ -80,50 +84,55 @@ class DatadogWebpackPlugin {
80
84
  }
81
85
  })
82
86
 
83
- const gitMetadata = getGitMetadata()
84
- if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
85
- const banner =
86
- 'if (typeof process === \'object\' && process !== null &&\n' +
87
- ' process.env !== null && typeof process.env === \'object\') {\n' +
88
- (gitMetadata.repositoryURL
89
- ? ` process.env.DD_GIT_REPOSITORY_URL = ${JSON.stringify(gitMetadata.repositoryURL)};\n`
90
- : '') +
91
- (gitMetadata.commitSHA
92
- ? ` process.env.DD_GIT_COMMIT_SHA = ${JSON.stringify(gitMetadata.commitSHA)};\n`
93
- : '') +
94
- '}\n'
95
-
96
- compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
97
- compilation.hooks.processAssets.tap(
98
- { name: PLUGIN_NAME, stage: -2000 },
99
- () => {
100
- for (const chunk of compilation.chunks) {
101
- if (!chunk.canBeInitial()) continue
102
- for (const filename of chunk.files) {
103
- if (!filename.endsWith('.js') && !filename.endsWith('.mjs')) continue
104
- compilation.updateAsset(filename, (old) => {
105
- const content = banner + old.source()
106
- return {
107
- source () { return content },
108
- size () { return Buffer.byteLength(content, 'utf8') },
109
- map () { return old.map() },
110
- sourceAndMap () { return { source: content, map: old.map() } },
111
- updateHash (hash) { hash.update(content) },
112
- }
113
- })
87
+ // Bake DD_GIT_* into assets unless DD_BUNDLER_INJECT_GIT_METADATA=false (stable artifact hashes).
88
+ if (DD_BUNDLER_INJECT_GIT_METADATA) {
89
+ const gitMetadata = getGitMetadata()
90
+ if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
91
+ const banner =
92
+ 'if (typeof process === \'object\' && process !== null &&\n' +
93
+ ' process.env !== null && typeof process.env === \'object\') {\n' +
94
+ (gitMetadata.repositoryURL
95
+ ? ` process.env.DD_GIT_REPOSITORY_URL = ${JSON.stringify(gitMetadata.repositoryURL)};\n`
96
+ : '') +
97
+ (gitMetadata.commitSHA
98
+ ? ` process.env.DD_GIT_COMMIT_SHA = ${JSON.stringify(gitMetadata.commitSHA)};\n`
99
+ : '') +
100
+ '}\n'
101
+
102
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
103
+ compilation.hooks.processAssets.tap(
104
+ { name: PLUGIN_NAME, stage: -2000 },
105
+ () => {
106
+ for (const chunk of compilation.chunks) {
107
+ if (!chunk.canBeInitial()) continue
108
+ for (const filename of chunk.files) {
109
+ if (!filename.endsWith('.js') && !filename.endsWith('.mjs')) continue
110
+ compilation.updateAsset(filename, (old) => {
111
+ const content = banner + old.source()
112
+ return {
113
+ source () { return content },
114
+ size () { return Buffer.byteLength(content, 'utf8') },
115
+ map () { return old.map() },
116
+ sourceAndMap () { return { source: content, map: old.map() } },
117
+ updateHash (hash) { hash.update(content) },
118
+ }
119
+ })
120
+ }
114
121
  }
115
122
  }
116
- }
117
- )
118
- })
123
+ )
124
+ })
119
125
 
120
- log.debug(
121
- 'Automatically injected git metadata (DD_GIT_REPOSITORY_URL: %s, DD_GIT_COMMIT_SHA: %s)',
122
- gitMetadata.repositoryURL || 'not available',
123
- gitMetadata.commitSHA || 'not available'
124
- )
126
+ log.debug(
127
+ 'Automatically injected git metadata (DD_GIT_REPOSITORY_URL: %s, DD_GIT_COMMIT_SHA: %s)',
128
+ gitMetadata.repositoryURL || 'not available',
129
+ gitMetadata.commitSHA || 'not available'
130
+ )
131
+ } else {
132
+ log.warn('No git metadata available - skipping injection')
133
+ }
125
134
  } else {
126
- log.warn('No git metadata available - skipping injection')
135
+ log.debug('Skipping git metadata injection (DD_BUNDLER_INJECT_GIT_METADATA=false)')
127
136
  }
128
137
 
129
138
  compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf) => {
@@ -146,8 +155,9 @@ class DatadogWebpackPlugin {
146
155
  }
147
156
 
148
157
  const request = resolveData.request
158
+ const isModuleOfInterest = modulesOfInterest.has(request) || modulesOfInterest.has(`${pkg}/${modulePath}`)
149
159
 
150
- if (!modulesOfInterest.has(request) && !modulesOfInterest.has(`${pkg}/${modulePath}`)) {
160
+ if (!isModuleOfInterest && getRewriteTarget(normalizedResource) === undefined) {
151
161
  return
152
162
  }
153
163
 
@@ -177,10 +187,12 @@ class DatadogWebpackPlugin {
177
187
  const version = packageJson.version
178
188
  const pkgPath = request === pkg ? pkg : `${pkg}/${modulePath}`
179
189
 
190
+ // Webpack runs loaders right-to-left, so appending makes this loader see the package's original source
191
+ // before any user transpiler lowers the syntax Orchestrion function queries match on.
180
192
  createData.loaders ||= []
181
- createData.loaders.unshift({
193
+ createData.loaders.push({
182
194
  loader: require.resolve('./src/loader'),
183
- options: { pkg, version, path: pkgPath },
195
+ options: { pkg, version, path: pkgPath, rewriteOnly: !isModuleOfInterest },
184
196
  })
185
197
 
186
198
  log.debug('LOAD: %s@%s, pkg "%s"', pkg, version, pkgPath)
@@ -1,26 +1,74 @@
1
1
  'use strict'
2
2
 
3
+ const { filename } = require('../../datadog-instrumentations/src/helpers/instrumentation-utils')
4
+ const { createBundlerRewriter } = require('../../datadog-instrumentations/src/helpers/rewriter')
5
+ const { getRewriteTarget } = require('../../datadog-instrumentations/src/helpers/rewriter/targets')
6
+
3
7
  const CHANNEL = 'dd-trace:bundler:load'
8
+ // Resolved from dd-trace so the injected require works even when dc-polyfill is not hoisted next to the target.
9
+ const DC_MODULE = require.resolve('dc-polyfill').replaceAll('\\', '/')
10
+ const DC_MODULE_LITERAL = JSON.stringify(DC_MODULE)
11
+
12
+ let rewrite
4
13
 
5
14
  /**
6
- * Webpack loader that appends a dc-polyfill channel publish to a CJS module.
7
- * Called for each module-of-interest identified by DatadogWebpackPlugin.
15
+ * Webpack loader called for each module-of-interest identified by DatadogWebpackPlugin.
16
+ * Orchestrion rewrite targets are rewritten and, for pure Orchestrion integrations, get an activation publish
17
+ * appended. Modules with export hooks get a dc-polyfill channel publish of their CJS exports appended.
8
18
  *
9
19
  * @param {string} source
20
+ * @param {string|object} [inputSourceMap]
10
21
  */
11
- module.exports = function loader (source) {
22
+ module.exports = function loader (source, inputSourceMap) {
12
23
  this.cacheable(false)
13
- const { pkg, version, path: pkgPath } = this.getOptions()
24
+ const { pkg, version, path: pkgPath, rewriteOnly } = this.getOptions()
25
+ const target = getRewriteTarget(this.resourcePath.replaceAll('\\', '/'))
26
+
27
+ let code = source
28
+ let map = inputSourceMap
29
+
30
+ if (target) {
31
+ rewrite ??= createBundlerRewriter(DC_MODULE)
32
+ const result = rewrite(source, this.resourcePath, 'commonjs', target, inputSourceMap)
33
+ code = result.code
34
+ map = result.map
35
+
36
+ if (code !== source && target.activate) {
37
+ const payload = JSON.stringify({
38
+ activate: true,
39
+ package: target.moduleName,
40
+ version,
41
+ path: filename(target.moduleName, target.filePath),
42
+ })
43
+ this.callback(
44
+ null,
45
+ `${code}\n;{\n` +
46
+ ` const __dd_ch = require(${DC_MODULE_LITERAL}).channel('${CHANNEL}');\n` +
47
+ ` if (__dd_ch.hasSubscribers) __dd_ch.publish(${payload});\n` +
48
+ '}\n',
49
+ map
50
+ )
51
+ return
52
+ }
53
+ }
54
+
55
+ // Rewrite-only targets have no export hook, so publishing their exports would only report a missing hook.
56
+ if (rewriteOnly) {
57
+ this.callback(null, code, map)
58
+ return
59
+ }
14
60
 
15
- return (
16
- source +
61
+ this.callback(
62
+ null,
63
+ code +
17
64
  '\n;{\n' +
18
- ' const __dd_dc = require(\'dc-polyfill\');\n' +
65
+ ` const __dd_dc = require(${DC_MODULE_LITERAL});\n` +
19
66
  ` const __dd_ch = __dd_dc.channel('${CHANNEL}');\n` +
20
67
  ' const __dd_mod = module.exports;\n' +
21
68
  ` const __dd_payload = { module: __dd_mod, version: '${version}', package: '${pkg}', path: '${pkgPath}' };\n` +
22
69
  ' __dd_ch.publish(__dd_payload);\n' +
23
70
  ' module.exports = __dd_payload.module;\n' +
24
- '}\n'
71
+ '}\n',
72
+ map
25
73
  )
26
74
  }
@@ -73,8 +73,7 @@ function onModelIntercept (ctx) {
73
73
  null,
74
74
  'AIGuard: unable to decode the model result: %s'
75
75
  )
76
- if (outputMessages === null) return result
77
- if (!outputMessages.length) return result
76
+ if (outputMessages === null || !outputMessages.length) return result
78
77
 
79
78
  return evaluate(ctx, aiguard, [outputMessages], opts).then(() => result)
80
79
  }
@@ -112,8 +111,7 @@ function interceptStreamedResult (ctx, result, inputMessages) {
112
111
  null,
113
112
  'AIGuard: unable to decode the streamed model result: %s'
114
113
  )
115
- if (outputMessages === null) return replayed
116
- if (!outputMessages.length) return replayed
114
+ if (outputMessages === null || !outputMessages.length) return replayed
117
115
 
118
116
  return evaluate(ctx, aiguard, [outputMessages], opts).then(() => replayed)
119
117
  })
@@ -10,8 +10,7 @@ const IMAGE_FALLBACK = '[image]'
10
10
  * @returns {string|undefined|null}
11
11
  */
12
12
  function stringifyIfNeeded (value) {
13
- if (value == null) return value
14
- if (typeof value === 'string') return value
13
+ if (value == null || typeof value === 'string') return value
15
14
 
16
15
  try {
17
16
  return JSON.stringify(value)
@@ -102,8 +102,14 @@ function resolveWritableString (root, path) {
102
102
  }
103
103
  }
104
104
 
105
- if (!node || typeof node !== 'object') return
106
- if (!Object.hasOwn(node, terminal.name) || typeof node[terminal.name] !== 'string') return
105
+ if (
106
+ !node ||
107
+ typeof node !== 'object' ||
108
+ !Object.hasOwn(node, terminal.name) ||
109
+ typeof node[terminal.name] !== 'string'
110
+ ) {
111
+ return
112
+ }
107
113
  return { container: node, key: terminal.name }
108
114
  }
109
115
 
@@ -383,8 +383,9 @@ function presenceBitmask (optionalGroups, present) {
383
383
  * @returns {string|null}
384
384
  */
385
385
  function normalizeRouteExpress (route, params, urlPath, parse, makeMatcher) {
386
- if (typeof route !== 'string' || !route) return null
387
- if (typeof parse !== 'function' || typeof makeMatcher !== 'function') return null
386
+ if (typeof route !== 'string' || !route || typeof parse !== 'function' || typeof makeMatcher !== 'function') {
387
+ return null
388
+ }
388
389
 
389
390
  let entry = routeCache.get(route)
390
391
  if (entry === undefined) {
@@ -58,9 +58,7 @@ function disable () {
58
58
  * @returns {'sample' | 'missing_route' | 'skip'}
59
59
  */
60
60
  function sampleRootSpanRequest (rootSpan, request, record = false) {
61
- if (!enabled) return SamplingDecision.SKIP
62
-
63
- if (!rootSpan) return SamplingDecision.SKIP
61
+ if (!enabled || !rootSpan) return SamplingDecision.SKIP
64
62
 
65
63
  const { method, statusCode, route, blocked = false } = request ?? {}
66
64
 
@@ -102,9 +100,7 @@ function sampleRequest (req, res, record = false) {
102
100
  if (!enabled) return SamplingDecision.SKIP
103
101
 
104
102
  const rootSpan = web.root(req)
105
- if (!rootSpan) return SamplingDecision.SKIP
106
-
107
- if (isRejected(rootSpan)) return SamplingDecision.SKIP
103
+ if (!rootSpan || isRejected(rootSpan)) return SamplingDecision.SKIP
108
104
 
109
105
  const statusCode = res.statusCode
110
106
  const route = getRouteOrEndpoint(web.getContext(req), statusCode)
@@ -163,15 +163,7 @@ function isRedirectResponse (res) {
163
163
  * @param {object} ctx http client instrumentation context (mutated).
164
164
  */
165
165
  function planResponseBodyCollection (originatingReq, res, ctx) {
166
- if (!config) {
167
- return
168
- }
169
-
170
- if (isRedirectResponse(res)) {
171
- return
172
- }
173
-
174
- if (!shouldSampleBody(originatingReq)) {
166
+ if (!config || isRedirectResponse(res) || !shouldSampleBody(originatingReq)) {
175
167
  return
176
168
  }
177
169
 
@@ -60,9 +60,7 @@ function onRequestCookieParser ({ req, res, abortController, cookies }) {
60
60
  if (!cookies || typeof cookies !== 'object') return
61
61
 
62
62
  const rootSpan = web.root(req)
63
- if (!rootSpan) return
64
-
65
- if (isEmpty(cookies)) return
63
+ if (!rootSpan || isEmpty(cookies)) return
66
64
  analyzedCookies.add(cookies)
67
65
 
68
66
  const results = waf.run({
@@ -189,9 +187,7 @@ function onRequestQueryParsed ({ req, res, query, abortController }) {
189
187
  }
190
188
 
191
189
  const rootSpan = web.root(req)
192
- if (!rootSpan) return
193
-
194
- if (isEmpty(query)) return
190
+ if (!rootSpan || isEmpty(query)) return
195
191
 
196
192
  const results = waf.run({
197
193
  persistent: {
@@ -204,9 +200,7 @@ function onRequestQueryParsed ({ req, res, query, abortController }) {
204
200
 
205
201
  function onRequestProcessParams ({ req, res, abortController, params }) {
206
202
  const rootSpan = web.root(req)
207
- if (!rootSpan) return
208
-
209
- if (!params || typeof params !== 'object' || isEmpty(params)) return
203
+ if (!rootSpan || !params || typeof params !== 'object' || isEmpty(params)) return
210
204
 
211
205
  const results = waf.run({
212
206
  persistent: {
@@ -11,8 +11,13 @@ const { storedResponseHeaders, copyHeadersOmitting } = require('./http-shared')
11
11
  const responseAnalyzedSet = new WeakSet()
12
12
 
13
13
  function onResponseBody ({ req, res, body }) {
14
- if (!body || typeof body !== 'object') return
15
- if (apiSecurity.sampleRequest(req, res) !== apiSecurity.SamplingDecision.SAMPLE) return
14
+ if (
15
+ !body ||
16
+ typeof body !== 'object' ||
17
+ apiSecurity.sampleRequest(req, res) !== apiSecurity.SamplingDecision.SAMPLE
18
+ ) {
19
+ return
20
+ }
16
21
 
17
22
  // we don't support blocking at this point, so no results needed
18
23
  waf.run({
@@ -2,6 +2,7 @@
2
2
 
3
3
  const path = require('path')
4
4
 
5
+ const { APPSEC_FS_STORAGE } = require('../../rasp/fs-plugin')
5
6
  const { getIastContext } = require('../iast-context')
6
7
  const { storage } = require('../../../../../datadog-core')
7
8
  const { PATH_TRAVERSAL } = require('../vulnerabilities')
@@ -29,10 +30,10 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
29
30
 
30
31
  onConfigure () {
31
32
  this.addSub('apm:fs:operation:start', (obj) => {
32
- const store = storage('legacy').getStore()
33
- const outOfReqOrChild = !store?.fs?.root
33
+ const fs = storage(APPSEC_FS_STORAGE).getStore()
34
+ const outOfReqOrChild = !fs?.root
34
35
 
35
- // we could filter out all the nested fs.operations based on store.fs.root
36
+ // we could filter out all the nested fs.operations based on fs.root
36
37
  // but if we spect a store in the context to be present we are going to exclude
37
38
  // all out_of_the_request fs.operations
38
39
  // AppsecFsPlugin must be enabled
@@ -17,10 +17,7 @@ class SetCookiesHeaderInterceptor extends Plugin {
17
17
 
18
18
  _handleCookies (name, value, res) {
19
19
  if (name.toLowerCase() === 'set-cookie') {
20
- let allCookies = value
21
- if (typeof value === 'string') {
22
- allCookies = [value]
23
- }
20
+ const allCookies = typeof value === 'string' ? [value] : value
24
21
  const alreadyCheckedCookies = this._getAlreadyCheckedCookiesInResponse(res)
25
22
 
26
23
  let location
@@ -3,9 +3,11 @@
3
3
  const TaintedUtils = require('@datadog/native-iast-taint-tracking')
4
4
  const { IAST_TRANSACTION_ID } = require('../iast-context')
5
5
  const log = require('../../../log')
6
+ const { typedArrayLength } = require('../../../util')
6
7
  const { HTTP_REQUEST_PARAMETER } = require('./source-types')
7
8
 
8
- const SEPARATOR = '\u0000' // Unit Separator (cannot be in URL keys)
9
+ const SEPARATOR = '\0' // Unit Separator (cannot be in URL keys)
10
+ const MAX_TRAVERSED_BINARY_VIEW_LENGTH = 1024
9
11
 
10
12
  function taintObject (iastContext, object, type) {
11
13
  let result = object
@@ -31,6 +33,10 @@ function taintObject (iastContext, object, type) {
31
33
  } else if (typeof value === 'object' && !visited.has(value)) {
32
34
  visited.add(value)
33
35
 
36
+ // Binary view elements are numbers, but named properties may hold strings. Their keys cannot be listed
37
+ // without also materializing one key per element, so only small views are walked.
38
+ if (ArrayBuffer.isView(value) && typedArrayLength(value) > MAX_TRAVERSED_BINARY_VIEW_LENGTH) continue
39
+
34
40
  for (const key of Object.keys(value)) {
35
41
  queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key], key })
36
42
  }
@@ -264,9 +264,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
264
264
  }
265
265
 
266
266
  _taintDatabaseResult (result, dbOrigin, iastContext, name) {
267
- if (!iastContext) return result
268
-
269
- if (this._rowsToTaint === 0) return result
267
+ if (!iastContext || this._rowsToTaint === 0) return result
270
268
 
271
269
  if (Array.isArray(result)) {
272
270
  for (let i = 0; i < result.length && i < this._rowsToTaint; i++) {
@@ -31,9 +31,7 @@ export async function initialize (data) {
31
31
  export async function load (url, context, nextLoad) {
32
32
  const result = await nextLoad(url, context)
33
33
 
34
- if (!port) return result
35
- if (!result.source) return result
36
- if (url.includes(ddTraceDir) || url.includes('iitm=true')) return result
34
+ if (!port || !result.source || url.includes(ddTraceDir) || url.includes('iitm=true')) return result
37
35
 
38
36
  let passes
39
37
  try {
@@ -24,12 +24,7 @@ function formatTags (tags, tagKey) {
24
24
  }
25
25
 
26
26
  function getNamespace (scope, context) {
27
- let namespace = globalNamespace
28
-
29
- if (scope === Scope.REQUEST) {
30
- namespace = getNamespaceFromContext(context) || globalNamespace
31
- }
32
- return namespace
27
+ return scope === Scope.REQUEST ? getNamespaceFromContext(context) || globalNamespace : globalNamespace
33
28
  }
34
29
 
35
30
  class IastMetric {
@@ -169,9 +169,7 @@ function parseResponseBody (rawBody, headers, isBase64Encoded) {
169
169
  // A handler that answered with the payload itself needs no parsing, and no content type either.
170
170
  if (typeof rawBody === 'object') return rawBody
171
171
 
172
- if (typeof rawBody !== 'string') return
173
-
174
- if (!isJsonContentType(headers?.['content-type'])) return
172
+ if (typeof rawBody !== 'string' || !isJsonContentType(headers?.['content-type'])) return
175
173
 
176
174
  if (isOverSizeCap(rawBody, isBase64Encoded)) {
177
175
  log.debug('[ASM] Lambda response body larger than %d bytes, skipping schema extraction',
@@ -1,11 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const Plugin = require('../../plugins/plugin')
3
+ const dc = require('dc-polyfill')
4
+
4
5
  const { storage } = require('../../../../datadog-core')
5
6
  const log = require('../../log')
6
7
 
7
8
  const RASP_MODULE = 'rasp'
8
9
  const IAST_MODULE = 'iast'
10
+ const APPSEC_FS_STORAGE = 'appsec-fs'
9
11
 
10
12
  const enabledFor = {
11
13
  [RASP_MODULE]: false,
@@ -13,42 +15,53 @@ const enabledFor = {
13
15
  }
14
16
 
15
17
  let fsPlugin
16
-
17
- function getStoreToStart (fsProps, store = storage('legacy').getStore()) {
18
- if (store && !store.fs?.opExcluded) {
18
+ const legacyStorage = storage('legacy')
19
+ const appsecFsStorage = storage(APPSEC_FS_STORAGE)
20
+ const fsOperationStart = dc.channel('apm:fs:operation:start')
21
+ const fsOperationFinish = dc.channel('apm:fs:operation:finish')
22
+ const responseRenderStart = dc.channel('tracing:datadog:express:response:render:start')
23
+ const responseRenderEnd = dc.channel('tracing:datadog:express:response:render:end')
24
+
25
+ function getStoreToStart (fsProps, store = appsecFsStorage.getStore()) {
26
+ // Only track operations inside a traced context, like a request. Callbacks of fs streams and watchers keep
27
+ // the state of the operation that created them, so state created outside of one would leak into every request
28
+ // of a server started from such a callback. The tracing fs plugin enters a noop store when no span is active.
29
+ const legacyStore = legacyStorage.getStore()
30
+ if (!legacyStore || legacyStore.noop) return
31
+
32
+ if (!store || !store.opExcluded) {
19
33
  return {
20
34
  ...store,
21
- fs: {
22
- ...store.fs,
23
- ...fsProps,
24
- parentStore: store,
25
- },
35
+ ...fsProps,
36
+ parentStore: store,
26
37
  }
27
38
  }
28
39
 
29
40
  return store
30
41
  }
31
42
 
32
- class AppsecFsPlugin extends Plugin {
43
+ class AppsecFsPlugin {
33
44
  enable () {
34
- this.addBind('apm:fs:operation:start', this._onFsOperationStart)
35
- this.addBind('apm:fs:operation:finish', this._onFsOperationFinishOrRenderEnd)
36
- this.addBind('tracing:datadog:express:response:render:start', this._onResponseRenderStart)
37
- this.addBind('tracing:datadog:express:response:render:end', this._onFsOperationFinishOrRenderEnd)
38
- // We might have to add the same subscribers for fastify later
39
-
40
- super.configure(true)
45
+ // The tracing fs plugin binds the legacy store on these channels. AppSec
46
+ // keeps its fs-only state in a dedicated storage namespace to avoid
47
+ // clobbering tracing state while still tracking nested operations.
48
+ fsOperationStart.bindStore(appsecFsStorage, this._onFsOperationStart)
49
+ fsOperationFinish.bindStore(appsecFsStorage, this._onFsOperationFinishOrRenderEnd)
50
+ responseRenderStart.bindStore(appsecFsStorage, this._onResponseRenderStart)
51
+ responseRenderEnd.bindStore(appsecFsStorage, this._onFsOperationFinishOrRenderEnd)
41
52
  }
42
53
 
43
54
  disable () {
44
- super.configure(false)
55
+ fsOperationStart.unbindStore(appsecFsStorage)
56
+ fsOperationFinish.unbindStore(appsecFsStorage)
57
+ responseRenderStart.unbindStore(appsecFsStorage)
58
+ responseRenderEnd.unbindStore(appsecFsStorage)
45
59
  }
46
60
 
47
61
  _onFsOperationStart () {
48
- const store = storage('legacy').getStore()
49
- if (store) {
50
- return getStoreToStart({ root: store.fs?.root === undefined }, store)
51
- }
62
+ const store = appsecFsStorage.getStore()
63
+
64
+ return getStoreToStart({ root: store?.root === undefined }, store)
52
65
  }
53
66
 
54
67
  _onResponseRenderStart () {
@@ -56,11 +69,7 @@ class AppsecFsPlugin extends Plugin {
56
69
  }
57
70
 
58
71
  _onFsOperationFinishOrRenderEnd () {
59
- const store = storage('legacy').getStore()
60
- if (store?.fs) {
61
- return store.fs.parentStore
62
- }
63
- return store
72
+ return appsecFsStorage.getStore()?.parentStore
64
73
  }
65
74
  }
66
75
 
@@ -97,6 +106,7 @@ module.exports = {
97
106
  disable,
98
107
 
99
108
  AppsecFsPlugin,
109
+ APPSEC_FS_STORAGE,
100
110
 
101
111
  RASP_MODULE,
102
112
  IAST_MODULE,
@@ -6,9 +6,14 @@ const { fsOperationStart, incomingHttpRequestStart, expressResponseRenderStart }
6
6
  const { storage } = require('../../../../datadog-core')
7
7
  const web = require('../../plugins/util/web')
8
8
  const { FS_OPERATION_PATH } = require('../addresses')
9
- const { getRequest } = require('../store')
9
+ const { getActiveRequest } = require('../store')
10
10
  const waf = require('../waf')
11
- const { enable: enableFsPlugin, disable: disableFsPlugin, RASP_MODULE } = require('./fs-plugin')
11
+ const {
12
+ APPSEC_FS_STORAGE,
13
+ enable: enableFsPlugin,
14
+ disable: disableFsPlugin,
15
+ RASP_MODULE,
16
+ } = require('./fs-plugin')
12
17
  const { RULE_TYPES, handleResult } = require('./utils')
13
18
 
14
19
  let config
@@ -59,11 +64,10 @@ function analyzeLfiInResponseRender (ctx) {
59
64
  }
60
65
 
61
66
  function analyzeLfi (ctx) {
62
- const store = storage('legacy').getStore()
63
- const fs = store?.fs
67
+ const fs = storage(APPSEC_FS_STORAGE).getStore()
64
68
  if (!fs) return
65
69
 
66
- const req = getRequest(store)
70
+ const req = getActiveRequest()
67
71
  if (!req) return
68
72
 
69
73
  const res = web.getContext(req)?.res