dd-trace 6.14.0 → 6.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/index.d.ts +8 -0
  2. package/package.json +3 -3
  3. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
  4. package/packages/datadog-instrumentations/src/jest.js +141 -25
  5. package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
  6. package/packages/datadog-instrumentations/src/playwright.js +4 -0
  7. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  8. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
  9. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  10. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
  11. package/packages/dd-trace/src/config/supported-configurations.json +2 -0
  12. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
  13. package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
  14. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
  15. package/packages/dd-trace/src/exporters/common/proxy.js +52 -0
  16. package/packages/dd-trace/src/exporters/common/request.js +11 -3
  17. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +5 -0
  18. package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
  19. package/packages/dd-trace/src/priority_sampler.js +4 -0
  20. package/packages/dd-trace/src/sampling_rule.js +3 -1
  21. package/packages/dd-trace/src/span_processor.js +18 -3
  22. package/packages/dd-trace/src/telemetry/send-data.js +5 -4
package/index.d.ts CHANGED
@@ -466,6 +466,13 @@ declare namespace tracer {
466
466
  * Maximum number of traces matching this rule to sample per second.
467
467
  */
468
468
  maxPerSecond?: number
469
+
470
+ /**
471
+ * When `true`, a trace chunk rejected by this rule is fully dropped:
472
+ * it is excluded from client-side stats and never sent to the Agent.
473
+ * @default false
474
+ */
475
+ discard?: boolean
469
476
  }
470
477
 
471
478
  /**
@@ -650,6 +657,7 @@ declare namespace tracer {
650
657
  * Sampling rules to apply to priority sampling. Each rule matches against a trace's
651
658
  * `service`, `name`, `resource`, and `tags`, and applies the rule's `sampleRate`. Use a
652
659
  * `sampleRate` of `0` to drop matching traces (for example to filter out unwanted resources).
660
+ * Specify `"discard": true` to fully drop it from stats as well.
653
661
  * If not specified, will defer to global sampling rate for all spans.
654
662
  * @default []
655
663
  * @env DD_TRACE_SAMPLING_RULES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.14.0",
3
+ "version": "6.15.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -183,8 +183,8 @@
183
183
  "opentracing": ">=0.14.7"
184
184
  },
185
185
  "optionalDependencies": {
186
- "@datadog/libdatadog": "^0.20.0",
187
- "@datadog/libdatadog-extras": "^0.20.0",
186
+ "@datadog/libdatadog": "^0.21.0",
187
+ "@datadog/libdatadog-extras": "^0.21.0",
188
188
  "@datadog/native-appsec": "11.0.2",
189
189
  "@datadog/native-iast-taint-tracking": "4.2.1",
190
190
  "@datadog/native-metrics": "4.0.0",
@@ -99,11 +99,7 @@ module.exports = [
99
99
  versionRange: '>=1.60.0',
100
100
  filePath: 'lib/coreBundle.js',
101
101
  },
102
- astQuery: 'AssignmentExpression[left.name="Page2"] > ClassExpression > ClassBody > ' +
103
- 'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async], ' +
104
- 'VariableDeclarator[id.name="Page2"] > ClassExpression > ClassBody > ' +
105
- 'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async], ' +
106
- 'ClassDeclaration[id.name="Page2"] > ClassBody > ' +
102
+ astQuery: 'ClassExpression[id.name="_Page"] > ClassBody > ' +
107
103
  'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async]',
108
104
  functionQuery: {
109
105
  methodName: 'goto',
@@ -11,7 +11,7 @@ const path = require('path')
11
11
  const satisfies = require('../../../vendor/dist/semifies')
12
12
  const { DD_MAJOR } = require('../../../version')
13
13
  const shimmer = require('../../datadog-shimmer')
14
- const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
14
+ const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
15
15
  const log = require('../../dd-trace/src/log')
16
16
  const {
17
17
  EMPTY_EFD_RETRY_POLICY,
@@ -64,6 +64,7 @@ const { addHook, channel } = require('./helpers/instrument')
64
64
  const testSessionStartCh = channel('ci:jest:session:start')
65
65
  const testSessionFinishCh = channel('ci:jest:session:finish')
66
66
  const codeCoverageReportCh = channel('ci:jest:coverage-report')
67
+ const bundlerLoadCh = channel('dd-trace:bundler:load')
67
68
 
68
69
  const testSessionConfigurationCh = channel('ci:jest:session:configuration')
69
70
 
@@ -179,7 +180,9 @@ const wrappedJestEsmLoaders = new WeakSet()
179
180
  const wrappedJestObjects = new WeakSet()
180
181
  const wrappedWorkerInitializers = new WeakSet()
181
182
  const publishedRuntimeReferenceErrors = new WeakMap()
182
- const jestEsmBypassModulePathsByRuntime = new WeakMap()
183
+ const jestEsmLoggingModulePathsByRuntime = new WeakMap()
184
+ const jestLoggingPackagesByRuntime = new WeakMap()
185
+ const instrumentedJestLoggingModules = new WeakMap()
183
186
  const wrappedCoverageReporters = new WeakSet()
184
187
  const coverageReporterRequires = new WeakMap()
185
188
  const handledJestEvents = new WeakSet()
@@ -3733,13 +3736,15 @@ if (DD_MAJOR < 6) {
3733
3736
  }, jestConfigSyncWrapper)
3734
3737
  }
3735
3738
 
3736
- const LOGGING_LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE = new Set([
3739
+ const JEST_LOGGING_LIBRARIES = new Set([
3737
3740
  'bunyan',
3738
3741
  'pino',
3739
3742
  'winston',
3740
3743
  ])
3744
+ const disabledJestInstrumentations = new Set(
3745
+ getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')?.split(',')
3746
+ )
3741
3747
  const LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE = new Set([
3742
- ...LOGGING_LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE,
3743
3748
  'selenium-webdriver',
3744
3749
  'selenium-webdriver/chrome',
3745
3750
  'selenium-webdriver/edge',
@@ -3884,13 +3889,17 @@ function requireOutsideJestRequireEngine (runtime, moduleName) {
3884
3889
  * @param {string} moduleName
3885
3890
  * @returns {void}
3886
3891
  */
3887
- function recordJestEsmBypassModulePath (runtime, from, moduleName) {
3888
- if (typeof from !== 'string' || !LOGGING_LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.has(moduleName)) return
3892
+ function recordJestEsmLoggingModulePath (runtime, from, moduleName) {
3893
+ if (
3894
+ typeof from !== 'string' ||
3895
+ !JEST_LOGGING_LIBRARIES.has(moduleName) ||
3896
+ disabledJestInstrumentations.has(moduleName)
3897
+ ) return
3889
3898
 
3890
- let pathsByParent = jestEsmBypassModulePathsByRuntime.get(runtime)
3899
+ let pathsByParent = jestEsmLoggingModulePathsByRuntime.get(runtime)
3891
3900
  if (!pathsByParent) {
3892
3901
  pathsByParent = new Map()
3893
- jestEsmBypassModulePathsByRuntime.set(runtime, pathsByParent)
3902
+ jestEsmLoggingModulePathsByRuntime.set(runtime, pathsByParent)
3894
3903
  }
3895
3904
 
3896
3905
  let modulePaths = pathsByParent.get(from)
@@ -3911,16 +3920,15 @@ function recordJestEsmBypassModulePath (runtime, from, moduleName) {
3911
3920
  * @param {object} runtime
3912
3921
  * @param {string} from
3913
3922
  * @param {string} modulePath
3914
- * @returns {boolean}
3923
+ * @returns {string | undefined}
3915
3924
  */
3916
- function hasJestEsmBypassModulePath (runtime, from, modulePath) {
3917
- const modulePaths = jestEsmBypassModulePathsByRuntime.get(runtime)?.get(from)
3918
- if (!modulePaths) return false
3925
+ function getJestEsmLoggingModuleName (runtime, from, modulePath) {
3926
+ const modulePaths = jestEsmLoggingModulePathsByRuntime.get(runtime)?.get(from)
3927
+ if (!modulePaths) return
3919
3928
 
3920
- for (const resolvedPath of modulePaths.values()) {
3921
- if (resolvedPath === modulePath) return true
3929
+ for (const [moduleName, resolvedPath] of modulePaths) {
3930
+ if (resolvedPath === modulePath) return moduleName
3922
3931
  }
3923
- return false
3924
3932
  }
3925
3933
 
3926
3934
  /**
@@ -3934,7 +3942,7 @@ function wrapJestEsmLoader (runtime) {
3934
3942
  wrappedJestEsmLoaders.add(esmLoader)
3935
3943
  if (typeof esmLoader.resolveModule === 'function') {
3936
3944
  shimmer.wrap(esmLoader, 'resolveModule', resolveModule => function (moduleName, from) {
3937
- recordJestEsmBypassModulePath(runtime, from, moduleName)
3945
+ recordJestEsmLoggingModulePath(runtime, from, moduleName)
3938
3946
  return resolveModule.apply(this, arguments)
3939
3947
  })
3940
3948
  }
@@ -3943,7 +3951,7 @@ function wrapJestEsmLoader (runtime) {
3943
3951
  esmLoader,
3944
3952
  'resolveSpecifierForSyncGraph',
3945
3953
  resolveSpecifier => function (from, moduleName) {
3946
- recordJestEsmBypassModulePath(runtime, from, moduleName)
3954
+ recordJestEsmLoggingModulePath(runtime, from, moduleName)
3947
3955
  return resolveSpecifier.apply(this, arguments)
3948
3956
  }
3949
3957
  )
@@ -3960,11 +3968,10 @@ function getJestBypassModulePath (runtime, from, moduleName) {
3960
3968
  if (typeof from !== 'string' || typeof moduleName !== 'string') return
3961
3969
 
3962
3970
  if (!LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.has(moduleName)) {
3963
- // Jest passes the resolved path when a CommonJS package is imported from an ESM test.
3964
- if (path.isAbsolute(moduleName) && hasJestEsmBypassModulePath(runtime, from, moduleName)) {
3965
- return moduleName
3966
- }
3967
- return
3971
+ // Keep the native fallback for logging packages that cannot be instrumented in Jest's realm, such as a
3972
+ // package-name-preserving symlink whose target is a user wrapper rather than the package itself.
3973
+ if (path.isAbsolute(moduleName) && getJestEsmLoggingModuleName(runtime, from, moduleName)) return moduleName
3974
+ if (!JEST_LOGGING_LIBRARIES.has(moduleName) || disabledJestInstrumentations.has(moduleName)) return
3968
3975
  }
3969
3976
 
3970
3977
  try {
@@ -3983,6 +3990,113 @@ function getJestBypassModulePath (runtime, from, moduleName) {
3983
3990
  }
3984
3991
  }
3985
3992
 
3993
+ /**
3994
+ * @param {object} runtime
3995
+ * @param {string} from
3996
+ * @param {string} moduleName
3997
+ * @returns {string}
3998
+ */
3999
+ function resolveJestModulePath (runtime, from, moduleName) {
4000
+ if (path.isAbsolute(moduleName)) return moduleName
4001
+
4002
+ if (typeof runtime._resolveCjsModule === 'function') {
4003
+ return runtime._resolveCjsModule(from, moduleName)
4004
+ } else if (typeof runtime.cjsLoader?.resolution?.resolveCjs === 'function') {
4005
+ return runtime.cjsLoader.resolution.resolveCjs(from, moduleName)
4006
+ }
4007
+ return runtime._resolveModule(from, moduleName)
4008
+ }
4009
+
4010
+ /**
4011
+ * @param {object} runtime
4012
+ * @param {string} from
4013
+ * @param {string} moduleName
4014
+ * @returns {{ name: string, path: string, version: string } | undefined}
4015
+ */
4016
+ function getJestLoggingPackage (runtime, from, moduleName) {
4017
+ if (typeof from !== 'string' || typeof moduleName !== 'string') return
4018
+
4019
+ const directModuleName = JEST_LOGGING_LIBRARIES.has(moduleName) && !disabledJestInstrumentations.has(moduleName)
4020
+ ? moduleName
4021
+ : getJestEsmLoggingModuleName(runtime, from, moduleName)
4022
+ const packages = jestLoggingPackagesByRuntime.get(runtime)
4023
+ let containingPackage
4024
+ if (!directModuleName) {
4025
+ if (!packages) return
4026
+
4027
+ const normalizedFrom = from.replaceAll(path.sep, '/')
4028
+ for (const [packageRoot, loggingPackage] of packages) {
4029
+ if (normalizedFrom.startsWith(`${packageRoot}/`)) {
4030
+ containingPackage = { packageRoot, ...loggingPackage }
4031
+ break
4032
+ }
4033
+ }
4034
+ if (!containingPackage) return
4035
+ }
4036
+
4037
+ try {
4038
+ const modulePath = resolveJestModulePath(runtime, from, moduleName)
4039
+ const normalizedModulePath = modulePath.replaceAll(path.sep, '/')
4040
+
4041
+ if (directModuleName) {
4042
+ if (modulePath !== createRequire(from).resolve(directModuleName)) return
4043
+
4044
+ const nodeModulesPath = `/node_modules/${directModuleName}/`
4045
+ const packagePathIndex = normalizedModulePath.lastIndexOf(nodeModulesPath)
4046
+ if (packagePathIndex === -1) return
4047
+
4048
+ const packageRoot = normalizedModulePath.slice(0, packagePathIndex + nodeModulesPath.length - 1)
4049
+ const { version } = JSON.parse(readFileSync(`${packageRoot}/package.json`, 'utf8'))
4050
+ if (typeof version !== 'string') return
4051
+
4052
+ let runtimePackages = packages
4053
+ if (!runtimePackages) {
4054
+ runtimePackages = new Map()
4055
+ jestLoggingPackagesByRuntime.set(runtime, runtimePackages)
4056
+ }
4057
+ runtimePackages.set(packageRoot, { name: directModuleName, version })
4058
+ return { name: directModuleName, path: directModuleName, version }
4059
+ }
4060
+
4061
+ const { packageRoot, name, version } = containingPackage
4062
+ if (!normalizedModulePath.startsWith(`${packageRoot}/`)) return
4063
+
4064
+ const modulePathWithinPackage = normalizedModulePath.slice(packageRoot.length + 1)
4065
+ return { name, path: `${name}/${modulePathWithinPackage}`, version }
4066
+ } catch {
4067
+ // Let Jest load unresolved, resolver-only, or virtual modules without instrumentation.
4068
+ }
4069
+ }
4070
+
4071
+ /**
4072
+ * @param {unknown} moduleExports
4073
+ * @param {{ name: string, path: string, version: string } | undefined} loggingPackage
4074
+ * @returns {unknown}
4075
+ */
4076
+ function instrumentJestLoggingModule (moduleExports, loggingPackage) {
4077
+ if (!loggingPackage || (typeof moduleExports !== 'object' && typeof moduleExports !== 'function')) {
4078
+ return moduleExports
4079
+ }
4080
+ if (moduleExports === null) return moduleExports
4081
+
4082
+ const instrumentedModule = instrumentedJestLoggingModules.get(moduleExports)
4083
+ if (instrumentedModule) return instrumentedModule
4084
+
4085
+ const payload = {
4086
+ module: moduleExports,
4087
+ package: loggingPackage.name,
4088
+ path: loggingPackage.path,
4089
+ version: loggingPackage.version,
4090
+ }
4091
+ // Apply the regular instrumentation after Jest has evaluated the module so its built-ins stay in Jest's realm.
4092
+ bundlerLoadCh.publish(payload)
4093
+ instrumentedJestLoggingModules.set(moduleExports, payload.module)
4094
+ if (payload.module && (typeof payload.module === 'object' || typeof payload.module === 'function')) {
4095
+ instrumentedJestLoggingModules.set(payload.module, payload.module)
4096
+ }
4097
+ return payload.module
4098
+ }
4099
+
3986
4100
  function formatDefaultStackTrace (error, structuredStackTrace) {
3987
4101
  const errorString = Error.prototype.toString.call(error)
3988
4102
  if (structuredStackTrace.length === 0) return errorString
@@ -4009,7 +4123,7 @@ addHook({
4009
4123
  // Jest 28 through 30.3 keeps ESM dependency resolution on Runtime itself.
4010
4124
  if (typeof Runtime.prototype.resolveModule === 'function') {
4011
4125
  shimmer.wrap(Runtime.prototype, 'resolveModule', resolveModule => function (moduleName, from) {
4012
- recordJestEsmBypassModulePath(this, from, moduleName)
4126
+ recordJestEsmLoggingModulePath(this, from, moduleName)
4013
4127
  return resolveModule.apply(this, arguments)
4014
4128
  })
4015
4129
  }
@@ -4024,11 +4138,13 @@ addHook({
4024
4138
  shimmer.wrap(Runtime.prototype, 'requireModule', requireModule => function (from, moduleName) {
4025
4139
  wrapJestGlobalsForRuntime(this)
4026
4140
  try {
4141
+ const loggingPackage = getJestLoggingPackage(this, from, moduleName)
4027
4142
  // Jest calls requireModule only after deciding that the module should not be mocked.
4028
- const bypassModulePath = getJestBypassModulePath(this, from, moduleName)
4029
- const returnedValue = bypassModulePath
4143
+ const bypassModulePath = loggingPackage ? undefined : getJestBypassModulePath(this, from, moduleName)
4144
+ let returnedValue = bypassModulePath
4030
4145
  ? requireOutsideJestRequireEngine(this, bypassModulePath)
4031
4146
  : requireModule.apply(this, arguments)
4147
+ returnedValue = instrumentJestLoggingModule(returnedValue, loggingPackage)
4032
4148
  if (moduleName === '@jest/globals') {
4033
4149
  wrapConcurrentJestGlobalsForRuntime(this, returnedValue)
4034
4150
  }
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { AsyncResource } = require('node:async_hooks')
4
+
3
5
  const { createCoverageMap } = require('../../../../vendor/dist/istanbul-lib-coverage')
4
6
  const satisfies = require('../../../../vendor/dist/semifies')
5
7
  const { DD_MAJOR } = require('../../../../version')
@@ -80,6 +82,8 @@ const runnerTestEndHandlers = new WeakMap()
80
82
  const runnerFailuresAdjusted = new WeakSet()
81
83
  const runnerFrameworkErrors = new WeakMap()
82
84
  const runnerStarted = new WeakSet()
85
+ const readyRunners = new WeakSet()
86
+ const pendingRunnerStarts = new WeakMap()
83
87
  const runnerRecoveryStates = new WeakMap()
84
88
  const runnersWithPendingCoverageReset = new WeakSet()
85
89
  const parallelRunners = new WeakSet()
@@ -1066,6 +1070,38 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
1066
1070
  runStoresWithCompletion(libraryConfigurationCh, ctx, onReceivedConfiguration)
1067
1071
  }
1068
1072
 
1073
+ /**
1074
+ * @param {import('mocha').Runner} runner
1075
+ * @returns {void}
1076
+ */
1077
+ function startMochaRunner (runner) {
1078
+ if (readyRunners.has(runner)) {
1079
+ runner.suite.run()
1080
+ } else {
1081
+ // Global setup can finish after configuration. Preserve the configuration
1082
+ // context until Runner#run has installed its delayed-start listener.
1083
+ pendingRunnerStarts.set(runner, AsyncResource.bind(() => runner.suite.run()))
1084
+ }
1085
+ }
1086
+
1087
+ /**
1088
+ * @param {import('mocha').Runner['run']} run
1089
+ * @param {import('mocha').Runner} runner
1090
+ * @param {Parameters<import('mocha').Runner['run']>} args
1091
+ * @returns {import('mocha').Runner}
1092
+ */
1093
+ function runMochaRunner (run, runner, args) {
1094
+ const result = run.apply(runner, args)
1095
+ // Once delay mode is enabled, startup must complete even if the plugin is disabled during global setup.
1096
+ readyRunners.add(runner)
1097
+ const start = pendingRunnerStarts.get(runner)
1098
+ if (start) {
1099
+ pendingRunnerStarts.delete(runner)
1100
+ start()
1101
+ }
1102
+ return result
1103
+ }
1104
+
1069
1105
  // In this hook we delay the execution with options.delay to grab library configuration,
1070
1106
  // skippable and known tests.
1071
1107
  // It is called but skipped in parallel mode.
@@ -1118,11 +1154,11 @@ function wrapMochaRun (Mocha, frameworkVersion) {
1118
1154
  getCodeCoverageCh.publish({
1119
1155
  onDone: (receivedCodeCoverage) => {
1120
1156
  untestedCoverage = receivedCodeCoverage
1121
- global.run()
1157
+ startMochaRunner(runner)
1122
1158
  },
1123
1159
  })
1124
1160
  } else {
1125
- global.run()
1161
+ startMochaRunner(runner)
1126
1162
  }
1127
1163
  })
1128
1164
 
@@ -1146,7 +1182,7 @@ addHook({
1146
1182
  const mocha = args[0]
1147
1183
 
1148
1184
  /**
1149
- * This attaches `run` to the global context, which we'll call after
1185
+ * This enables the delayed root suite, which we'll release after
1150
1186
  * our configuration and skippable suites requests.
1151
1187
  * You need this both here and in Mocha#run hook: the programmatic API
1152
1188
  * does not call `runMocha`, so it needs to be in Mocha#run. When using
@@ -1178,7 +1214,7 @@ addHook({
1178
1214
 
1179
1215
  shimmer.wrap(Runner.prototype, 'run', run => function (...args) {
1180
1216
  if (!testFinishCh.hasSubscribers) {
1181
- return run.apply(this, args)
1217
+ return runMochaRunner(run, this, args)
1182
1218
  }
1183
1219
 
1184
1220
  const { onRunDone, onFlushDone } = getRunCompletionCallbacks(args[0])
@@ -1511,7 +1547,7 @@ addHook({
1511
1547
  }
1512
1548
  })
1513
1549
 
1514
- return run.apply(this, args)
1550
+ return runMochaRunner(run, this, args)
1515
1551
  })
1516
1552
 
1517
1553
  return Runner
@@ -81,6 +81,7 @@ const testsToTestStatuses = new Map()
81
81
  const activeRumPages = new Set()
82
82
 
83
83
  const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
84
+ const isPlaywrightWorker = getValueFromEnvSources('DD_PLAYWRIGHT_WORKER') === '1'
84
85
  const DD_PROPERTIES_TIMEOUT = 5000
85
86
  const isFailureScreenshotUploadEnabled =
86
87
  getValueFromEnvSources('DD_TEST_FAILURE_SCREENSHOTS_ENABLED') === true
@@ -1712,6 +1713,9 @@ createRootSuiteCh.subscribe({
1712
1713
 
1713
1714
  pageGotoCh.subscribe({
1714
1715
  asyncEnd (ctx) {
1716
+ // Playwright library consumers such as Vitest have no Playwright test span and may navigate during page startup.
1717
+ if (!isPlaywrightWorker) return
1718
+
1715
1719
  // The Page.goto rewriter waits for this so tests closing immediately after navigation still get RUM tags.
1716
1720
  const rumDetectionPromise = handlePageGoto(ctx.self)
1717
1721
  ctx.resolveCallback = onDone => rumDetectionPromise.then(onDone, onDone)
@@ -2,7 +2,9 @@
2
2
 
3
3
  const log = require('../../../../../log')
4
4
 
5
- const COMMAND_PATTERN = String.raw`^(?:\s*(?:sudo|doas)\s+)?\b\S+\b\s(.*)`
5
+ // `\S+\s` accepts command tokens that end in punctuation. The optional prefix uses horizontal
6
+ // whitespace so multiline matching cannot start inside a line-terminator run.
7
+ const COMMAND_PATTERN = String.raw`^(?:[ \t]*(?:sudo|doas)[ \t]+)?\S+\s([\s\S]*)`
6
8
  const pattern = new RegExp(COMMAND_PATTERN, 'gmi')
7
9
 
8
10
  module.exports = function extractSensitiveRanges (evidence) {