dd-trace 6.8.0 → 6.9.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 (179) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +9 -3
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +30 -34
  44. package/index.d.ts +98 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +5 -3
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  55. package/packages/datadog-instrumentations/src/dns.js +21 -2
  56. package/packages/datadog-instrumentations/src/electron.js +1 -0
  57. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  58. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  59. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  60. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +33 -9
  62. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +118 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  66. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  67. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  68. package/packages/datadog-instrumentations/src/jest.js +979 -233
  69. package/packages/datadog-instrumentations/src/mocha/main.js +107 -9
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +64 -42
  71. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  72. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  73. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  74. package/packages/datadog-instrumentations/src/next.js +155 -23
  75. package/packages/datadog-instrumentations/src/openai.js +14 -0
  76. package/packages/datadog-instrumentations/src/pg.js +180 -6
  77. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  78. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  79. package/packages/datadog-instrumentations/src/promise.js +3 -3
  80. package/packages/datadog-instrumentations/src/router.js +195 -19
  81. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -10
  82. package/packages/datadog-instrumentations/src/vitest-main.js +360 -56
  83. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  84. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  85. package/packages/datadog-instrumentations/src/webdriverio.js +194 -18
  86. package/packages/datadog-instrumentations/src/when.js +3 -3
  87. package/packages/datadog-instrumentations/src/ws.js +5 -1
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  89. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  90. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  91. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  92. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  93. package/packages/datadog-plugin-electron/src/net.js +10 -4
  94. package/packages/datadog-plugin-http/src/client.js +1 -1
  95. package/packages/datadog-plugin-http2/src/client.js +1 -1
  96. package/packages/datadog-plugin-jest/src/index.js +1 -18
  97. package/packages/datadog-plugin-jest/src/util.js +2 -0
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  99. package/packages/datadog-plugin-mocha/src/index.js +318 -1
  100. package/packages/datadog-plugin-next/src/index.js +57 -9
  101. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  102. package/packages/datadog-plugin-pg/src/index.js +65 -1
  103. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  104. package/packages/datadog-plugin-vitest/src/index.js +95 -7
  105. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  106. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  107. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  108. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  109. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  110. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  111. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  112. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  113. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  115. package/packages/dd-trace/src/appsec/index.js +48 -476
  116. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  117. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  118. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  120. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  122. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  123. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  124. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  127. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  128. package/packages/dd-trace/src/config/helper.js +2 -0
  129. package/packages/dd-trace/src/config/index.js +16 -5
  130. package/packages/dd-trace/src/config/remote_config.js +1 -1
  131. package/packages/dd-trace/src/config/supported-configurations.json +8 -0
  132. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  133. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  134. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  135. package/packages/dd-trace/src/guardrails/log.js +1 -10
  136. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  137. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  138. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  139. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  140. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  141. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  142. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  143. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  144. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  145. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  146. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  147. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  148. package/packages/dd-trace/src/log/channels.js +1 -9
  149. package/packages/dd-trace/src/log/levels.js +12 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  151. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  153. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  154. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  155. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  156. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  157. package/packages/dd-trace/src/plugins/index.js +1 -0
  158. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  159. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  160. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  161. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  162. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  163. package/packages/dd-trace/src/plugins/util/test.js +319 -75
  164. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  165. package/packages/dd-trace/src/plugins/util/web.js +23 -1
  166. package/packages/dd-trace/src/profiler.js +1 -1
  167. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  168. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  169. package/packages/dd-trace/src/proxy.js +1 -0
  170. package/packages/dd-trace/src/ritm.js +5 -0
  171. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  172. package/packages/dd-trace/src/sampling_rule.js +4 -2
  173. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  174. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  175. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  176. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  177. package/vendor/dist/pprof-format/index.js +1 -1
  178. package/version.js +10 -8
  179. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -7,6 +7,7 @@ const {
7
7
  TEST_STATUS,
8
8
  TEST_TYPE,
9
9
  VITEST_POOL,
10
+ addIntelligentTestRunnerSpanTags,
10
11
  finishAllTraceSpans,
11
12
  getTestSuitePath,
12
13
  getTestSuiteCommonTags,
@@ -15,7 +16,6 @@ const {
15
16
  getIsFaultyEarlyFlakeDetection,
16
17
  TEST_SOURCE_FILE,
17
18
  TEST_IS_RETRY,
18
- TEST_CODE_COVERAGE_LINES_PCT,
19
19
  TEST_CODE_OWNERS,
20
20
  TEST_COMMAND,
21
21
  TEST_LEVELS_METADATA,
@@ -41,11 +41,21 @@ const {
41
41
  TEST_BROWSER_NAME,
42
42
  TEST_IS_RUM_ACTIVE,
43
43
  TEST_PARAMETERS,
44
+ TEST_ITR_UNSKIPPABLE,
45
+ TEST_ITR_FORCED_RUN,
46
+ ITR_CORRELATION_ID,
44
47
  } = require('../../dd-trace/src/plugins/util/test')
45
48
  const { COMPONENT } = require('../../dd-trace/src/constants')
49
+ const id = require('../../dd-trace/src/id')
46
50
  const {
47
51
  TELEMETRY_EVENT_CREATED,
48
52
  TELEMETRY_EVENT_FINISHED,
53
+ TELEMETRY_CODE_COVERAGE_STARTED,
54
+ TELEMETRY_CODE_COVERAGE_FINISHED,
55
+ TELEMETRY_CODE_COVERAGE_EMPTY,
56
+ TELEMETRY_CODE_COVERAGE_NUM_FILES,
57
+ TELEMETRY_ITR_FORCED_TO_RUN,
58
+ TELEMETRY_ITR_UNSKIPPABLE,
49
59
  TELEMETRY_TEST_SESSION,
50
60
  } = require('../../dd-trace/src/ci-visibility/telemetry')
51
61
  const { DD_MAJOR } = require('../../../version')
@@ -343,7 +353,9 @@ class VitestPlugin extends CiPlugin {
343
353
  testName,
344
354
  testSuiteAbsolutePath,
345
355
  isNew,
356
+ isAttemptToFix,
346
357
  isDisabled,
358
+ isQuarantined,
347
359
  isRumActive,
348
360
  isTestFrameworkWorker,
349
361
  requestErrorTags,
@@ -362,7 +374,9 @@ class VitestPlugin extends CiPlugin {
362
374
  [TEST_SOURCE_START]: testStartLine || 1,
363
375
  [TEST_STATUS]: 'skip',
364
376
  [TEST_FINAL_STATUS]: 'skip',
377
+ ...(isAttemptToFix ? { [TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX]: 'true' } : {}),
365
378
  ...(isDisabled ? { [TEST_MANAGEMENT_IS_DISABLED]: 'true' } : {}),
379
+ ...(isQuarantined ? { [TEST_MANAGEMENT_IS_QUARANTINED]: 'true' } : {}),
366
380
  ...(isNew ? { [TEST_IS_NEW]: 'true' } : {}),
367
381
  ...(isRumActive ? { [TEST_IS_RUM_ACTIVE]: 'true' } : {}),
368
382
  ...(isTestFrameworkWorker ? { [TEST_IS_TEST_FRAMEWORK_WORKER]: 'true' } : {}),
@@ -393,10 +407,16 @@ class VitestPlugin extends CiPlugin {
393
407
  frameworkVersion,
394
408
  isTestFrameworkWorker,
395
409
  isVitestNoWorkerInitActive,
410
+ disableTestImpactAnalysis,
396
411
  isBrowserMode,
397
412
  browserDriver,
398
413
  browserName,
399
414
  browserProjectName,
415
+ isCodeCoverageEnabled,
416
+ coverageLibrary,
417
+ itrCorrelationId,
418
+ isUnskippable,
419
+ isForcedToRun,
400
420
  } = ctx
401
421
 
402
422
  const testCommand = ctx.testCommand || 'vitest run'
@@ -416,6 +436,7 @@ class VitestPlugin extends CiPlugin {
416
436
  const testSessionName = getTestSessionName(this.config, trimmedCommand, this.testEnvironmentMetadata)
417
437
  if (this.tracer._exporter.addMetadataTags) {
418
438
  const libraryCapabilitiesTags = this.getLibraryCapabilitiesTags(frameworkVersion, {
439
+ disableTestImpactAnalysis,
419
440
  isVitestNoWorkerInitActive,
420
441
  })
421
442
  this.tracer._exporter.addMetadataTags({
@@ -444,6 +465,20 @@ class VitestPlugin extends CiPlugin {
444
465
  if (isTestFrameworkWorker) {
445
466
  testSuiteMetadata[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
446
467
  }
468
+ if (itrCorrelationId) {
469
+ testSuiteMetadata[ITR_CORRELATION_ID] = itrCorrelationId
470
+ }
471
+ if (isUnskippable) {
472
+ testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
473
+ this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
474
+ }
475
+ if (isForcedToRun) {
476
+ testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
477
+ this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
478
+ }
479
+ if (isCodeCoverageEnabled) {
480
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: coverageLibrary })
481
+ }
447
482
  setBrowserTags(testSuiteMetadata, {
448
483
  browserDriver,
449
484
  browserName,
@@ -473,9 +508,33 @@ class VitestPlugin extends CiPlugin {
473
508
  return ctx.currentStore
474
509
  })
475
510
 
476
- this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, deferFlush, onDone }) => {
511
+ this.addSub('ci:vitest:test-suite:finish', ({
512
+ testSuiteSpan,
513
+ status,
514
+ coverageFiles,
515
+ coverageLibrary,
516
+ testSuiteAbsolutePath,
517
+ deferFlush,
518
+ onDone,
519
+ }) => {
477
520
  if (testSuiteSpan) {
478
521
  testSuiteSpan.setTag(TEST_STATUS, status)
522
+ if (coverageFiles) {
523
+ if (!coverageFiles.length) {
524
+ this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
525
+ }
526
+ const files = new Set(coverageFiles)
527
+ files.add(testSuiteAbsolutePath)
528
+ const relativeFiles = [...files].map(filename => getTestSuitePath(filename, this.repositoryRoot))
529
+ const { _traceId, _spanId } = testSuiteSpan.context()
530
+ this.tracer._exporter.exportCoverage({
531
+ sessionId: _traceId,
532
+ suiteId: _spanId,
533
+ files: relativeFiles,
534
+ })
535
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: coverageLibrary })
536
+ this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeFiles.length)
537
+ }
479
538
  testSuiteSpan.finish()
480
539
  finishAllTraceSpans(testSuiteSpan)
481
540
  }
@@ -490,6 +549,17 @@ class VitestPlugin extends CiPlugin {
490
549
  }
491
550
  })
492
551
 
552
+ this.addSub('ci:vitest:worker-report:coverage', data => {
553
+ const formattedCoverages = JSON.parse(data).map(coverage => ({
554
+ sessionId: id(coverage.sessionId),
555
+ suiteId: id(coverage.suiteId),
556
+ files: coverage.files,
557
+ }))
558
+ for (const formattedCoverage of formattedCoverages) {
559
+ this.tracer._exporter.exportCoverage(formattedCoverage)
560
+ }
561
+ })
562
+
493
563
  this.addBind('ci:vitest:test-suite:error', (ctx) => {
494
564
  const { error } = ctx
495
565
  const testSuiteSpan = ctx.currentStore?.testSuiteSpan
@@ -512,12 +582,18 @@ class VitestPlugin extends CiPlugin {
512
582
  isEarlyFlakeDetectionEnabled,
513
583
  isEarlyFlakeDetectionFaulty,
514
584
  isTestManagementTestsEnabled,
585
+ isCodeCoverageEnabled,
586
+ isSuitesSkippingEnabled,
587
+ isSuitesSkipped,
588
+ numSkippedSuites,
589
+ hasUnskippableSuites,
590
+ hasForcedToRunSuites,
515
591
  requestErrorTags,
516
592
  vitestPool,
517
593
  isVitestNoWorkerInitActive,
518
594
  onDone,
519
595
  }) => {
520
- for (const [tag, value] of Object.entries(requestErrorTags || {})) {
596
+ for (const [tag, value] of Object.entries(requestErrorTags)) {
521
597
  this.testSessionSpan.setTag(tag, value)
522
598
  this.testModuleSpan.setTag(tag, value)
523
599
  }
@@ -527,10 +603,20 @@ class VitestPlugin extends CiPlugin {
527
603
  this.testModuleSpan.setTag('error', error)
528
604
  this.testSessionSpan.setTag('error', error)
529
605
  }
530
- if (testCodeCoverageLinesTotal !== undefined) {
531
- this.testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
532
- this.testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
533
- }
606
+ addIntelligentTestRunnerSpanTags(
607
+ this.testSessionSpan,
608
+ this.testModuleSpan,
609
+ {
610
+ isSuitesSkipped,
611
+ isSuitesSkippingEnabled,
612
+ isCodeCoverageEnabled,
613
+ testCodeCoverageLinesTotal,
614
+ skippingType: 'suite',
615
+ skippingCount: numSkippedSuites,
616
+ hasUnskippableSuites,
617
+ hasForcedToRunSuites,
618
+ }
619
+ )
534
620
  if (isEarlyFlakeDetectionEnabled) {
535
621
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
536
622
  }
@@ -566,12 +652,14 @@ class VitestPlugin extends CiPlugin {
566
652
  * Returns Vitest library capability metadata tags.
567
653
  * @param {string} frameworkVersion - The Vitest version.
568
654
  * @param {object} [ctx] - Diagnostic channel context.
655
+ * @param {boolean} [ctx.disableTestImpactAnalysis] - Whether TIA is unsupported for this run.
569
656
  * @param {boolean} [ctx.isVitestNoWorkerInitActive] - Whether no-worker init is active for this run.
570
657
  * @returns {Record<string, string|undefined>}
571
658
  */
572
659
  getLibraryCapabilitiesTags (frameworkVersion, ctx = {}) {
573
660
  return getDefaultLibraryCapabilitiesTags(this.constructor.id, frameworkVersion, {
574
661
  omitFailedTestReplay: ctx.isVitestNoWorkerInitActive,
662
+ omitTestImpactAnalysis: ctx.disableTestImpactAnalysis || ctx.isVitestNoWorkerInitActive,
575
663
  })
576
664
  }
577
665
 
@@ -15,6 +15,10 @@ const skipMethodSize = skipMethods.size
15
15
 
16
16
  const nonConfigurableModuleExports = new WeakMap()
17
17
 
18
+ /**
19
+ * @typedef {NonNullable<ReturnType<typeof globalThis.Object.getOwnPropertyDescriptor>>} Descriptor
20
+ */
21
+
18
22
  // Reused descriptor scratch space for the `name` and `length` slots that
19
23
  // `copyProperties` and `wrapCallback` rewrite per wrap. `Object.defineProperty`
20
24
  // reads the descriptor's slots synchronously and does not retain the object,
@@ -46,7 +50,7 @@ function copyProperties (original, wrapped) {
46
50
  if (ownKeys.length !== 2) {
47
51
  for (const key of ownKeys) {
48
52
  if (skipMethods.has(key)) continue
49
- const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(original, key))
53
+ const descriptor = /** @type {Descriptor} */ (Object.getOwnPropertyDescriptor(original, key))
50
54
  if (descriptor.writable && descriptor.enumerable && descriptor.configurable) {
51
55
  wrapped[key] = original[key]
52
56
  } else if (descriptor.writable || descriptor.configurable || !Object.hasOwn(wrapped, key)) {
@@ -65,7 +69,7 @@ function copyObjectProperties (original, wrapped, skipKey) {
65
69
  const ownKeys = Reflect.ownKeys(original)
66
70
  for (const key of ownKeys) {
67
71
  if (key === skipKey) continue
68
- const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(original, key))
72
+ const descriptor = /** @type {Descriptor} */ (Object.getOwnPropertyDescriptor(original, key))
69
73
  if (descriptor.writable && descriptor.enumerable && descriptor.configurable) {
70
74
  wrapped[key] = original[key]
71
75
  } else if (descriptor.writable || descriptor.configurable || !Object.hasOwn(wrapped, key)) {
@@ -121,6 +121,9 @@ class AIGuard extends NoopAIGuard {
121
121
  *
122
122
  * - Clones each message so callers cannot mutate the data set in the meta struct.
123
123
  * - Truncates the list of messages and `content` fields emitting metrics accordingly.
124
+ *
125
+ * @param {import('../../../../index').aiguard.Message[]} messages
126
+ * @param {{ source: string, integration: string }} telemetryTags
124
127
  */
125
128
  #buildMessagesForMetaStruct (messages, telemetryTags) {
126
129
  const size = Math.min(messages.length, this.#maxMessagesLength)
@@ -1,10 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { LRUCache } = require('../../../../vendor/dist/lru-cache')
4
- const log = require('../log')
5
- const web = require('../plugins/util/web')
6
- const blockedTemplates = require('./blocked_templates')
7
- const { updateBlockFailureMetric } = require('./telemetry')
3
+ const { LRUCache } = require('../../../../../vendor/dist/lru-cache')
4
+ const log = require('../../log')
5
+ const web = require('../../plugins/util/web')
6
+ const { updateBlockFailureMetric } = require('../telemetry')
7
+ const blockedTemplates = require('./templates')
8
8
 
9
9
  // Bounded by the LRU as defense-in-depth: getSpecificKey already keys on the
10
10
  // resolved route (or the path with the query string stripped) so cardinality
@@ -182,7 +182,26 @@ function getBlockingAction (actions) {
182
182
  }
183
183
 
184
184
  /**
185
- * @param {import('../config/config-base')} [config] - Tracer configuration
185
+ * Turns a WAF result's actions into a blocking response, when one is requested.
186
+ * Single seam from WAF output to {@link block}, shared by every request/response/auth handler.
187
+ *
188
+ * @param {Record<string, unknown>|undefined} actions WAF result actions.
189
+ * @param {import('http').IncomingMessage} req
190
+ * @param {import('http').ServerResponse} res
191
+ * @param {object} rootSpan
192
+ * @param {AbortController} abortController
193
+ */
194
+ function handleResults (actions, req, res, rootSpan, abortController) {
195
+ if (!actions || !req || !res || !rootSpan || !abortController) return
196
+
197
+ const blockingAction = getBlockingAction(actions)
198
+ if (blockingAction) {
199
+ block(req, res, rootSpan, abortController, blockingAction)
200
+ }
201
+ }
202
+
203
+ /**
204
+ * @param {import('../../config/config-base')} [config] - Tracer configuration
186
205
  */
187
206
  function setTemplates (config) {
188
207
  templates.html.body = config?.appsec?.blockedTemplateHtml
@@ -233,6 +252,7 @@ module.exports = {
233
252
  specificBlockingTypes,
234
253
  getBlockingData,
235
254
  getBlockingAction,
255
+ handleResults,
236
256
  setTemplates,
237
257
  isBlocked,
238
258
  setDefaultBlockingActionParameters,
@@ -0,0 +1,62 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+ const web = require('../../plugins/util/web')
5
+ const addresses = require('../addresses')
6
+ const { handleResults } = require('../blocking')
7
+ const { getActiveRequest } = require('../store')
8
+ const UserTracking = require('../user_tracking')
9
+ const waf = require('../waf')
10
+
11
+ function onPassportVerify ({ framework, login, user, success, abortController }) {
12
+ const req = getActiveRequest()
13
+ const rootSpan = req && web.root(req)
14
+
15
+ if (!rootSpan) {
16
+ log.warn('[ASM] No rootSpan found in onPassportVerify')
17
+ return
18
+ }
19
+
20
+ const results = UserTracking.trackLogin(framework, login, user, success, rootSpan)
21
+
22
+ handleResults(results?.actions, req, web.getContext(req)?.res, rootSpan, abortController)
23
+ }
24
+
25
+ function onPassportDeserializeUser ({ user, abortController }) {
26
+ const req = getActiveRequest()
27
+ const rootSpan = req && web.root(req)
28
+
29
+ if (!rootSpan) {
30
+ log.warn('[ASM] No rootSpan found in onPassportDeserializeUser')
31
+ return
32
+ }
33
+
34
+ const results = UserTracking.trackUser(user, rootSpan)
35
+
36
+ handleResults(results?.actions, req, web.getContext(req)?.res, rootSpan, abortController)
37
+ }
38
+
39
+ function onExpressSession ({ req, res, sessionId, abortController }) {
40
+ const rootSpan = web.root(req)
41
+ if (!rootSpan) {
42
+ log.warn('[ASM] No rootSpan found in onExpressSession')
43
+ return
44
+ }
45
+
46
+ const isSdkCalled = rootSpan.context().getTag('usr.session_id')
47
+ if (isSdkCalled) return
48
+
49
+ const results = waf.run({
50
+ persistent: {
51
+ [addresses.USER_SESSION_ID]: sessionId,
52
+ },
53
+ }, req)
54
+
55
+ handleResults(results?.actions, req, res, rootSpan, abortController)
56
+ }
57
+
58
+ module.exports = {
59
+ onPassportVerify,
60
+ onPassportDeserializeUser,
61
+ onExpressSession,
62
+ }
@@ -0,0 +1,215 @@
1
+ 'use strict'
2
+
3
+ const { HTTP_CLIENT_IP } = require('../../../../../ext/tags')
4
+ const web = require('../../plugins/util/web')
5
+ const { extractIp } = require('../../plugins/util/ip_extractor')
6
+ const { isEmpty } = require('../../util')
7
+ const addresses = require('../addresses')
8
+ const apiSecurity = require('../api_security')
9
+ const { handleResults } = require('../blocking')
10
+ const Reporter = require('../reporter')
11
+ const { getActiveRequest } = require('../store')
12
+ const waf = require('../waf')
13
+ const { storedResponseHeaders, copyHeadersOmitting } = require('./http-shared')
14
+
15
+ const analyzedBodies = new WeakSet()
16
+ const analyzedCookies = new WeakSet()
17
+ const storedBodies = new WeakMap()
18
+
19
+ let config
20
+
21
+ /**
22
+ * @param {import('../../config/config-base')|undefined} _config
23
+ */
24
+ function setConfig (_config) {
25
+ config = _config
26
+ }
27
+
28
+ function onRequestBodyParsed ({ req, res, body, abortController }) {
29
+ if (body === undefined || body === null) return
30
+
31
+ if (!req) {
32
+ req = getActiveRequest()
33
+ }
34
+
35
+ const rootSpan = web.root(req)
36
+ if (!rootSpan) return
37
+
38
+ if (!req.body) {
39
+ // do not store body if it is in req.body
40
+ storedBodies.set(req, body)
41
+ }
42
+
43
+ if (typeof body === 'object') {
44
+ if (isEmpty(body)) return
45
+ analyzedBodies.add(body)
46
+ }
47
+
48
+ const results = waf.run({
49
+ persistent: {
50
+ [addresses.HTTP_INCOMING_BODY]: body,
51
+ },
52
+ }, req)
53
+
54
+ handleResults(results?.actions, req, res, rootSpan, abortController)
55
+ }
56
+
57
+ function onRequestCookieParser ({ req, res, abortController, cookies }) {
58
+ if (!cookies || typeof cookies !== 'object') return
59
+
60
+ const rootSpan = web.root(req)
61
+ if (!rootSpan) return
62
+
63
+ if (isEmpty(cookies)) return
64
+ analyzedCookies.add(cookies)
65
+
66
+ const results = waf.run({
67
+ persistent: {
68
+ [addresses.HTTP_INCOMING_COOKIES]: cookies,
69
+ },
70
+ }, req)
71
+
72
+ handleResults(results?.actions, req, res, rootSpan, abortController)
73
+ }
74
+
75
+ function incomingHttpStartTranslator ({ req, res, abortController }) {
76
+ const rootSpan = web.root(req)
77
+ if (!rootSpan) return
78
+
79
+ const clientIp = extractIp(config, req)
80
+
81
+ rootSpan.addTags({
82
+ '_dd.appsec.enabled': 1,
83
+ '_dd.runtime_family': 'nodejs',
84
+ [HTTP_CLIENT_IP]: clientIp,
85
+ })
86
+
87
+ if (config.inferredProxyServicesEnabled) {
88
+ const context = web.getContext(req)
89
+ if (context?.inferredProxySpan) {
90
+ context.inferredProxySpan.setTag('_dd.appsec.enabled', 1)
91
+ }
92
+ }
93
+
94
+ const persistent = {
95
+ [addresses.HTTP_INCOMING_URL]: req.url,
96
+ [addresses.HTTP_INCOMING_HEADERS]: copyHeadersOmitting(req.headers, 'cookie'),
97
+ [addresses.HTTP_INCOMING_METHOD]: req.method,
98
+ }
99
+
100
+ if (clientIp) {
101
+ persistent[addresses.HTTP_CLIENT_IP] = clientIp
102
+ }
103
+
104
+ const results = waf.run({ persistent }, req)
105
+
106
+ handleResults(results?.actions, req, res, rootSpan, abortController)
107
+ }
108
+
109
+ function incomingHttpEndTranslator ({ req, res }) {
110
+ const persistent = {}
111
+
112
+ // we need to keep this to support other body parsers
113
+ if (req.body !== undefined && req.body !== null) {
114
+ if (typeof req.body === 'object') {
115
+ if (!isEmpty(req.body) && !analyzedBodies.has(req.body)) {
116
+ persistent[addresses.HTTP_INCOMING_BODY] = req.body
117
+ }
118
+ } else {
119
+ persistent[addresses.HTTP_INCOMING_BODY] = req.body
120
+ }
121
+ }
122
+
123
+ // we need to keep this to support other cookie parsers
124
+ if (
125
+ req.cookies !== null &&
126
+ typeof req.cookies === 'object' &&
127
+ !isEmpty(req.cookies) &&
128
+ !analyzedCookies.has(req.cookies)
129
+ ) {
130
+ persistent[addresses.HTTP_INCOMING_COOKIES] = req.cookies
131
+ }
132
+
133
+ // we need to keep this to support nextjs
134
+ const query = req.query
135
+ if (
136
+ query !== null &&
137
+ typeof query === 'object' &&
138
+ !isEmpty(query)
139
+ ) {
140
+ persistent[addresses.HTTP_INCOMING_QUERY] = query
141
+ }
142
+
143
+ // This hook runs before span finish, so ensure route/endpoint tags are available before API Security sampling runs.
144
+ web.setRouteOrEndpointTag(req)
145
+
146
+ const apiSecSamplingDecision = apiSecurity.sampleRequest(req, res, true)
147
+ if (apiSecSamplingDecision === apiSecurity.SamplingDecision.SAMPLE) {
148
+ persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
149
+ }
150
+
151
+ let wafResult
152
+ if (!isEmpty(persistent)) {
153
+ wafResult = waf.run({ persistent }, req)
154
+ }
155
+
156
+ apiSecurity.reportRequest(req, apiSecSamplingDecision, wafResult)
157
+
158
+ waf.disposeContext(req)
159
+
160
+ const storedHeaders = storedResponseHeaders.get(req) || {}
161
+
162
+ const body = req.body || storedBodies.get(req)
163
+ Reporter.finishRequest(req, res, storedHeaders, body)
164
+
165
+ if (storedHeaders) {
166
+ storedResponseHeaders.delete(req)
167
+ }
168
+ storedBodies.delete(req)
169
+ }
170
+
171
+ function onRequestQueryParsed ({ req, res, query, abortController }) {
172
+ if (!query || typeof query !== 'object') return
173
+
174
+ if (!req) {
175
+ req = getActiveRequest()
176
+ }
177
+
178
+ const rootSpan = web.root(req)
179
+ if (!rootSpan) return
180
+
181
+ if (isEmpty(query)) return
182
+
183
+ const results = waf.run({
184
+ persistent: {
185
+ [addresses.HTTP_INCOMING_QUERY]: query,
186
+ },
187
+ }, req)
188
+
189
+ handleResults(results?.actions, req, res, rootSpan, abortController)
190
+ }
191
+
192
+ function onRequestProcessParams ({ req, res, abortController, params }) {
193
+ const rootSpan = web.root(req)
194
+ if (!rootSpan) return
195
+
196
+ if (!params || typeof params !== 'object' || isEmpty(params)) return
197
+
198
+ const results = waf.run({
199
+ persistent: {
200
+ [addresses.HTTP_INCOMING_PARAMS]: params,
201
+ },
202
+ }, req)
203
+
204
+ handleResults(results?.actions, req, res, rootSpan, abortController)
205
+ }
206
+
207
+ module.exports = {
208
+ setConfig,
209
+ onRequestBodyParsed,
210
+ onRequestCookieParser,
211
+ incomingHttpStartTranslator,
212
+ incomingHttpEndTranslator,
213
+ onRequestQueryParsed,
214
+ onRequestProcessParams,
215
+ }
@@ -0,0 +1,78 @@
1
+ 'use strict'
2
+
3
+ const web = require('../../plugins/util/web')
4
+ const { isEmpty } = require('../../util')
5
+ const addresses = require('../addresses')
6
+ const apiSecurity = require('../api_security')
7
+ const { isBlocked, callBlockDelegation, handleResults } = require('../blocking')
8
+ const waf = require('../waf')
9
+ const { storedResponseHeaders, copyHeadersOmitting } = require('./http-shared')
10
+
11
+ const responseAnalyzedSet = new WeakSet()
12
+
13
+ function onResponseBody ({ req, res, body }) {
14
+ if (!body || typeof body !== 'object') return
15
+ if (apiSecurity.sampleRequest(req, res) !== apiSecurity.SamplingDecision.SAMPLE) return
16
+
17
+ // we don't support blocking at this point, so no results needed
18
+ waf.run({
19
+ persistent: {
20
+ [addresses.HTTP_INCOMING_RESPONSE_BODY]: body,
21
+ },
22
+ }, req)
23
+ }
24
+
25
+ function onResponseWriteHead ({ req, res, abortController, statusCode, responseHeaders }) {
26
+ // Normalize header names to lowercase so downstream consumers see the same shape
27
+ // regardless of how the caller wrote them.
28
+ const normalizedResponseHeaders = {}
29
+ for (const [key, value] of Object.entries(responseHeaders)) {
30
+ normalizedResponseHeaders[key.toLowerCase()] = value
31
+ }
32
+
33
+ if (!isEmpty(normalizedResponseHeaders)) {
34
+ storedResponseHeaders.set(req, normalizedResponseHeaders)
35
+ }
36
+
37
+ // TODO: do not call waf if inside block()
38
+ // if (isBlocking()) {
39
+ // return
40
+ // }
41
+
42
+ // avoid "write after end" error
43
+ if (isBlocked(res) || callBlockDelegation(res)) {
44
+ abortController?.abort()
45
+ return
46
+ }
47
+
48
+ // avoid double waf call
49
+ if (responseAnalyzedSet.has(res)) {
50
+ return
51
+ }
52
+
53
+ const rootSpan = web.root(req)
54
+ if (!rootSpan) return
55
+
56
+ const results = waf.run({
57
+ persistent: {
58
+ [addresses.HTTP_INCOMING_RESPONSE_CODE]: String(statusCode),
59
+ [addresses.HTTP_INCOMING_RESPONSE_HEADERS]: copyHeadersOmitting(normalizedResponseHeaders, 'set-cookie'),
60
+ },
61
+ }, req)
62
+
63
+ responseAnalyzedSet.add(res)
64
+
65
+ handleResults(results?.actions, req, res, rootSpan, abortController)
66
+ }
67
+
68
+ function onResponseOperation ({ res, abortController }) {
69
+ if (isBlocked(res)) {
70
+ abortController?.abort()
71
+ }
72
+ }
73
+
74
+ module.exports = {
75
+ onResponseBody,
76
+ onResponseWriteHead,
77
+ onResponseOperation,
78
+ }
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ // Response headers captured on `responseWriteHead` (http-response) and later read
4
+ // when the request finishes (http-request end translator). This is the only piece
5
+ // of per-request state shared across the two handler modules.
6
+ const storedResponseHeaders = new WeakMap()
7
+
8
+ /**
9
+ * @param {Record<string, unknown>} src
10
+ * @param {string} omit
11
+ * @returns {Record<string, unknown>}
12
+ */
13
+ function copyHeadersOmitting (src, omit) {
14
+ const filtered = {}
15
+ for (const key of Object.keys(src)) {
16
+ if (key !== omit) filtered[key] = src[key]
17
+ }
18
+ return filtered
19
+ }
20
+
21
+ module.exports = {
22
+ storedResponseHeaders,
23
+ copyHeadersOmitting,
24
+ }