dd-trace 6.4.0 → 6.5.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 (178) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +10 -7
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +20 -7
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  65. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  67. package/packages/datadog-instrumentations/src/jest.js +26 -0
  68. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  69. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  71. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  72. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  73. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  74. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  75. package/packages/datadog-instrumentations/src/next.js +24 -0
  76. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  77. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  78. package/packages/datadog-instrumentations/src/pg.js +2 -2
  79. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  80. package/packages/datadog-instrumentations/src/process.js +3 -0
  81. package/packages/datadog-instrumentations/src/router.js +18 -8
  82. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  83. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  84. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  85. package/packages/datadog-instrumentations/src/ws.js +2 -1
  86. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  87. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  88. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  89. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  90. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  91. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  92. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  93. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  94. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  95. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  96. package/packages/datadog-plugin-http2/src/client.js +1 -1
  97. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  99. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  100. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  101. package/packages/datadog-plugin-router/src/index.js +11 -2
  102. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  103. package/packages/datadog-plugin-ws/src/close.js +2 -1
  104. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  105. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  106. package/packages/datadog-plugin-ws/src/server.js +2 -1
  107. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  109. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  110. package/packages/dd-trace/src/azure_metadata.js +2 -1
  111. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  112. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  113. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  114. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  115. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  116. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  117. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  119. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  120. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  121. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  122. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  123. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  124. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  127. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  128. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  129. package/packages/dd-trace/src/config/helper.js +1 -0
  130. package/packages/dd-trace/src/config/index.js +10 -0
  131. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  132. package/packages/dd-trace/src/exporter.js +2 -0
  133. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  134. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  135. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  136. package/packages/dd-trace/src/feature-registry.js +10 -3
  137. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  138. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  139. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  140. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  141. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  142. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  143. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  144. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  145. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  146. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  147. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  148. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  149. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  150. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  151. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  152. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  153. package/packages/dd-trace/src/llmobs/util.js +32 -0
  154. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  155. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  156. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  157. package/packages/dd-trace/src/openfeature/index.js +0 -2
  158. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  159. package/packages/dd-trace/src/openfeature/register.js +16 -27
  160. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  161. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  162. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  163. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  165. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  166. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  167. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  168. package/packages/dd-trace/src/proxy.js +84 -7
  169. package/packages/dd-trace/src/span_processor.js +5 -0
  170. package/packages/dd-trace/src/standalone/index.js +0 -14
  171. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  172. package/packages/dd-trace/src/util.js +26 -0
  173. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  174. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  175. package/vendor/dist/protobufjs/index.js +1 -1
  176. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  177. package/vendor/dist/shell-quote/index.js +1 -1
  178. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -0,0 +1,114 @@
1
+ 'use strict'
2
+
3
+ const { getCommandBlocker } = require('./command-blocker')
4
+ const { runCommand, serializeDisplayCommand } = require('./command-runner')
5
+ const { getDatadogCleanCommand } = require('./local-command')
6
+ const { getBasicReportingCommand, summarizeTestOutput } = require('./scenarios/basic-reporting')
7
+ const { frameworkOutDir } = require('./scenarios/helpers')
8
+ const { getObservedTestCount } = require('./test-output')
9
+
10
+ /**
11
+ * Runs the selected Basic Reporting command without inherited Datadog initialization.
12
+ *
13
+ * @param {object} input preflight inputs
14
+ * @param {object} input.framework manifest framework entry
15
+ * @param {string} input.out validation output directory
16
+ * @param {object} input.options validator options
17
+ * @returns {Promise<{ok: boolean, failure?: object, preflight: object}>} preflight outcome
18
+ */
19
+ async function runFrameworkPreflight ({ framework, out, options }) {
20
+ const maxTestCount = framework.preflight.maxTestCount
21
+ const command = getDatadogCleanCommand(getBasicReportingCommand(framework))
22
+ const outDir = frameworkOutDir(out, framework, 'preflight')
23
+ const result = await runCommand(command, {
24
+ artifactRoot: out,
25
+ envMode: 'clean',
26
+ label: `${framework.id}:preflight`,
27
+ outDir,
28
+ repositoryRoot: options.repositoryRoot,
29
+ requireExecutableApproval: options.requireExecutableApproval,
30
+ verbose: options.verbose,
31
+ })
32
+ const observedTestCount = getObservedTestCount(framework.framework, result.stdout, result.stderr)
33
+ const preflight = {
34
+ ran: true,
35
+ source: 'validator',
36
+ maxTestCount,
37
+ command: serializeDisplayCommand(command),
38
+ exitCode: result.exitCode,
39
+ durationMs: result.durationMs,
40
+ observedTestCount,
41
+ stdoutSummary: summarizeTestOutput(result.stdout).join('\n'),
42
+ stderrSummary: summarizeTestOutput('', result.stderr).join('\n'),
43
+ }
44
+ framework.preflight = preflight
45
+
46
+ const testCountKnown = Number.isInteger(observedTestCount)
47
+ const scopeMatched = testCountKnown && observedTestCount >= 1 && observedTestCount <= maxTestCount
48
+ preflight.scopeMatched = scopeMatched
49
+
50
+ if (!result.timedOut && scopeMatched && (result.exitCode === 0 || observedTestCount > 0)) {
51
+ return { ok: true, preflight }
52
+ }
53
+
54
+ const commandFailure = getCommandBlocker(result)
55
+ const diagnosis = commandFailure?.summary || getPreflightFailureDiagnosis({
56
+ maxTestCount,
57
+ observedTestCount,
58
+ result,
59
+ testCountKnown,
60
+ })
61
+
62
+ return {
63
+ ok: false,
64
+ preflight,
65
+ failure: {
66
+ frameworkId: framework.id,
67
+ scenario: 'basic-reporting',
68
+ status: commandFailure ? 'blocked' : 'error',
69
+ diagnosis,
70
+ evidence: {
71
+ commandExitCode: result.exitCode,
72
+ commandTimedOut: result.timedOut,
73
+ representativeScopeMismatch: !commandFailure && !scopeMatched,
74
+ ...(commandFailure ? { commandFailure } : {}),
75
+ preflight,
76
+ },
77
+ artifacts: Object.values(result.artifacts),
78
+ },
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Produces the narrowest diagnosis supported by a failed clean preflight.
84
+ *
85
+ * @param {object} input diagnosis inputs
86
+ * @param {number} input.maxTestCount approved representative test limit
87
+ * @param {number|undefined} input.observedTestCount parsed test count
88
+ * @param {object} input.result command result
89
+ * @param {boolean} input.testCountKnown whether the test count was parsed
90
+ * @returns {string} customer-facing diagnosis
91
+ */
92
+ function getPreflightFailureDiagnosis ({ maxTestCount, observedTestCount, result, testCountKnown }) {
93
+ if (result.timedOut) {
94
+ return 'The selected test command timed out during the validator-controlled uninstrumented preflight. No Test ' +
95
+ 'Optimization conclusion was reached.'
96
+ }
97
+ if (!testCountKnown) {
98
+ return 'The validator could not determine how many tests the selected command ran, so it could not confirm ' +
99
+ `the approved representative scope of at most ${maxTestCount} tests. Select a command whose output ` +
100
+ 'reports the test count before validating Test Optimization.'
101
+ }
102
+ if (observedTestCount > maxTestCount) {
103
+ return `The selected command ran ${observedTestCount} tests, exceeding the approved representative scope ` +
104
+ `of at most ${maxTestCount}. Select a narrower test command before validating Test Optimization.`
105
+ }
106
+ if (observedTestCount < 1) {
107
+ return 'The selected command did not report any tests. Select a runnable representative before validating ' +
108
+ 'Test Optimization.'
109
+ }
110
+ return 'The selected test command failed before the validator could confirm that tests ran without Datadog ' +
111
+ 'initialization. Fix the project command or its setup before validating Test Optimization.'
112
+ }
113
+
114
+ module.exports = { runFrameworkPreflight }
@@ -0,0 +1,331 @@
1
+ 'use strict'
2
+
3
+ const REDACTED = '<redacted>'
4
+ const MAX_SANITIZE_DEPTH = 128
5
+ const TRUNCATED_NESTING = '[Truncated: nesting exceeds redaction limit]'
6
+
7
+ const SECRET_NAME_SOURCE = [
8
+ 'API_?KEY',
9
+ 'APP_?KEY',
10
+ 'TOKEN',
11
+ 'SECRET',
12
+ 'PASSWORD',
13
+ 'PASSPHRASE',
14
+ 'CREDENTIAL',
15
+ 'PRIVATE_?KEY',
16
+ 'CLIENT_?SECRET',
17
+ 'ACCESS_?KEY',
18
+ 'COOKIE',
19
+ ].join('|')
20
+ const EXACT_SECRET_ASSIGNMENT_NAME_SOURCE = [
21
+ SECRET_NAME_SOURCE,
22
+ 'AUTH',
23
+ 'AUTHORIZATION',
24
+ 'PASS',
25
+ 'SET-COOKIE',
26
+ 'PAT',
27
+ 'JWT',
28
+ 'WEBHOOK(?:_URL)?',
29
+ ].join('|')
30
+ const SECRET_NAME_CHARS = '[A-Za-z0-9_.-]'
31
+ const SECRET_ASSIGNMENT_NAME_SOURCE = [
32
+ `(?:${EXACT_SECRET_ASSIGNMENT_NAME_SOURCE})`,
33
+ `[A-Za-z_]${SECRET_NAME_CHARS}*(?:${SECRET_NAME_SOURCE})${SECRET_NAME_CHARS}*`,
34
+ `[A-Za-z_]${SECRET_NAME_CHARS}*[-_]PASS`,
35
+ `[A-Za-z_]${SECRET_NAME_CHARS}*[-_]AUTH(?:ORIZATION)?`,
36
+ `[A-Za-z_]${SECRET_NAME_CHARS}*[-_](?:PAT|JWT|WEBHOOK(?:_URL)?)`,
37
+ 'PASS',
38
+ 'AUTH',
39
+ 'AUTHORIZATION',
40
+ 'COOKIE',
41
+ 'SET-COOKIE',
42
+ ].join('|')
43
+ const SECRET_FLAG_SOURCE = [
44
+ 'api-key',
45
+ 'app-key',
46
+ 'token',
47
+ 'secret',
48
+ 'password',
49
+ 'pass',
50
+ 'credential',
51
+ 'private-key',
52
+ 'client-secret',
53
+ 'access-key',
54
+ 'auth',
55
+ ].join('|')
56
+ const SECRET_VALUE_SOURCE = String.raw`("[^"]*"|'[^']*'|[^\s,;]+)`
57
+ const SENSITIVE_NAME_PATTERN = new RegExp(`(?:${SECRET_NAME_SOURCE})`, 'i')
58
+ const SENSITIVE_AUTH_NAME_PATTERN = /(?:^|_)AUTH(?:ORIZATION)?(?:_|$)/i
59
+ const SENSITIVE_COOKIE_NAME_PATTERN = /(?:^|_)SET_?COOKIE(?:_|$)|(?:^|_)COOKIE(?:_|$)/i
60
+ const SENSITIVE_PASS_NAME_PATTERN = /(?:^|_)PASS(?:_|$)/i
61
+ const SENSITIVE_TOKEN_ALIAS_NAME_PATTERN = /(?:^|_)(?:PAT|JWT|WEBHOOK(?:_URL)?)(?:_|$)/i
62
+ const SECRET_ASSIGNMENT_PATTERN = new RegExp(
63
+ String.raw`\b(${SECRET_ASSIGNMENT_NAME_SOURCE})\s*=\s*` + SECRET_VALUE_SOURCE,
64
+ 'gi'
65
+ )
66
+ const SECRET_FLAG_PATTERN = new RegExp(
67
+ String.raw`(--(?:${SECRET_FLAG_SOURCE})(?:-[A-Za-z0-9]+)*)(=|\s+)` + SECRET_VALUE_SOURCE,
68
+ 'gi'
69
+ )
70
+ const SECRET_FLAG_NAME_PATTERN = new RegExp(
71
+ `^--(?:${SECRET_FLAG_SOURCE})(?:-[A-Za-z0-9]+)*$`,
72
+ 'i'
73
+ )
74
+ const AUTH_HEADER_PATTERN = /\b(Bearer)\s+([^\s'",}\]]+)/gi
75
+ const AUTH_SCHEME_ASSIGNMENT_QUOTED_PATTERN = new RegExp(
76
+ String.raw`\b(${SECRET_ASSIGNMENT_NAME_SOURCE})\s*=\s*(["'])(?:Bearer|Basic)\s+.*?\2`,
77
+ 'gi'
78
+ )
79
+ const AUTH_SCHEME_ASSIGNMENT_PATTERN = new RegExp(
80
+ String.raw`\b(${SECRET_ASSIGNMENT_NAME_SOURCE})\s*=\s*(?:Bearer|Basic)\s+[^\s,;]+`,
81
+ 'gi'
82
+ )
83
+ const DEFAULT_IGNORABLE_PATTERN = /\p{Default_Ignorable_Code_Point}/gu
84
+ const DEFAULT_IGNORABLE_TEST_PATTERN = /\p{Default_Ignorable_Code_Point}/u
85
+ const CONTROL_CHARACTER_TEST_PATTERN = /\p{Cc}/u
86
+ const UNSAFE_CONTROL_SOURCE = String.raw`[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]`
87
+ const UNSAFE_CONTROL_PATTERN = new RegExp(UNSAFE_CONTROL_SOURCE, 'g')
88
+ const UNSAFE_CONTROL_TEST_PATTERN = new RegExp(UNSAFE_CONTROL_SOURCE)
89
+ const PRIVATE_KEY_BLOCK_PATTERN =
90
+ /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g
91
+ const JWT_VALUE_PATTERN = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
92
+ const KNOWN_TOKEN_VALUE_PATTERN =
93
+ /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{20,})\b/g
94
+ const SECRET_HEADER_ENV_NAME_SOURCE = [
95
+ `(?:${SECRET_NAME_SOURCE}|PAT|JWT|WEBHOOK(?:_URL)?)`,
96
+ `[A-Za-z_]${SECRET_NAME_CHARS}*(?:${SECRET_NAME_SOURCE})${SECRET_NAME_CHARS}*`,
97
+ `[A-Za-z_]${SECRET_NAME_CHARS}*[-_]AUTH(?:ORIZATION)?`,
98
+ `[A-Za-z_]${SECRET_NAME_CHARS}*[-_](?:PAT|JWT|WEBHOOK(?:_URL)?)`,
99
+ ].join('|')
100
+ const SECRET_HEADER_NAME_SOURCE = [
101
+ 'dd-api-key',
102
+ 'x-api-key',
103
+ 'api-key',
104
+ 'authorization',
105
+ 'proxy-authorization',
106
+ 'token',
107
+ 'cookie',
108
+ 'set-cookie',
109
+ SECRET_HEADER_ENV_NAME_SOURCE,
110
+ ].join('|')
111
+ const SECRET_HEADER_PATTERN = new RegExp(
112
+ String.raw`\b((?:${SECRET_HEADER_NAME_SOURCE}))\s*:\s*("[^"]*"|'[^']*'|[^\r\n,}]+)`,
113
+ 'gi'
114
+ )
115
+ const URL_CREDENTIAL_PATTERN = /([a-z][a-z0-9+.-]*:\/\/)([^@\s/]+)(@)/gi
116
+ const GITHUB_SECRET_REFERENCE_PATTERN =
117
+ /(?:\b[A-Za-z_][A-Za-z0-9_.-]*\s*[:=]\s*)?\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g
118
+ const SAFE_REFERENCE_MARKER_PATTERN = /DDCIVARREF([0-9]+)X/g
119
+
120
+ const ENV_CONTAINER_KEYS = new Set([
121
+ 'safeEnv',
122
+ 'workflowEnv',
123
+ 'jobEnv',
124
+ 'stepEnv',
125
+ 'inheritedEnv',
126
+ ])
127
+
128
+ const SECRET_NAME_ONLY_KEYS = new Set([
129
+ 'requiredEnvVars',
130
+ 'requiredSecretEnvVars',
131
+ 'secretEnvVars',
132
+ 'missingEnvVars',
133
+ 'originalSecretEnvVars',
134
+ ])
135
+
136
+ /**
137
+ * Redacts secret-like values from arbitrary report data before it is serialized.
138
+ *
139
+ * @param {unknown} value data to sanitize
140
+ * @returns {unknown} sanitized copy
141
+ */
142
+ function sanitizeForReport (value) {
143
+ return sanitizeValue(value, new WeakSet(), undefined, 0)
144
+ }
145
+
146
+ /**
147
+ * Redacts secret-like values from an environment variable map.
148
+ *
149
+ * @param {object|undefined} env environment map
150
+ * @returns {object|undefined} sanitized environment map
151
+ */
152
+ function sanitizeEnv (env) {
153
+ if (!env || typeof env !== 'object' || Array.isArray(env)) return
154
+
155
+ const sanitized = {}
156
+ for (const [name, value] of Object.entries(env)) {
157
+ sanitized[name] = sanitizeEnvValue(name, value)
158
+ }
159
+ if (Object.keys(sanitized).length > 0) return sanitized
160
+ }
161
+
162
+ /**
163
+ * Redacts a single environment value when its name is secret-like.
164
+ *
165
+ * @param {string} name environment variable name
166
+ * @param {unknown} value environment variable value
167
+ * @returns {string|undefined} sanitized environment variable value
168
+ */
169
+ function sanitizeEnvValue (name, value) {
170
+ if (isSensitiveName(name)) return REDACTED
171
+ if (value === undefined) return
172
+ return sanitizeString(String(value))
173
+ }
174
+
175
+ /**
176
+ * Redacts common inline secret forms from strings.
177
+ *
178
+ * @param {string} value string to sanitize
179
+ * @returns {string} sanitized string
180
+ */
181
+ function sanitizeString (value) {
182
+ const references = []
183
+ const protectedValue = value.replaceAll(GITHUB_SECRET_REFERENCE_PATTERN, reference => {
184
+ return `DDCIVARREF${references.push(reference) - 1}X`
185
+ })
186
+ const sanitized = protectedValue
187
+ .replaceAll(DEFAULT_IGNORABLE_PATTERN, '')
188
+ .replaceAll(UNSAFE_CONTROL_PATTERN, '')
189
+ .replaceAll(PRIVATE_KEY_BLOCK_PATTERN, '<redacted-private-key>')
190
+ .replaceAll(JWT_VALUE_PATTERN, REDACTED)
191
+ .replaceAll(KNOWN_TOKEN_VALUE_PATTERN, REDACTED)
192
+ .replaceAll(AUTH_SCHEME_ASSIGNMENT_QUOTED_PATTERN, `$1=${REDACTED}`)
193
+ .replaceAll(AUTH_SCHEME_ASSIGNMENT_PATTERN, `$1=${REDACTED}`)
194
+ .replaceAll(SECRET_ASSIGNMENT_PATTERN, `$1=${REDACTED}`)
195
+ .replaceAll(SECRET_FLAG_PATTERN, `$1$2${REDACTED}`)
196
+ .replaceAll(SECRET_HEADER_PATTERN, `$1: ${REDACTED}`)
197
+ .replaceAll(AUTH_HEADER_PATTERN, `$1 ${REDACTED}`)
198
+ .replaceAll(URL_CREDENTIAL_PATTERN, `$1${REDACTED}$3`)
199
+
200
+ return sanitized.replaceAll(SAFE_REFERENCE_MARKER_PATTERN, (marker, index) => references[Number(index)] || marker)
201
+ }
202
+
203
+ /**
204
+ * Detects default-ignorable Unicode characters that can conceal executable text or secret names.
205
+ *
206
+ * @param {unknown} value candidate text
207
+ * @returns {boolean} true when the text contains a default-ignorable Unicode character
208
+ */
209
+ function hasUnicodeDefaultIgnorable (value) {
210
+ return DEFAULT_IGNORABLE_TEST_PATTERN.test(String(value ?? ''))
211
+ }
212
+
213
+ function hasUnsafeInvisibleCharacter (value) {
214
+ const text = String(value ?? '')
215
+ return hasUnicodeDefaultIgnorable(text) || UNSAFE_CONTROL_TEST_PATTERN.test(text)
216
+ }
217
+
218
+ function hasUnsafeExecutionCharacter (value) {
219
+ return hasUnicodeDefaultIgnorable(value) || CONTROL_CHARACTER_TEST_PATTERN.test(String(value ?? ''))
220
+ }
221
+
222
+ /**
223
+ * Makes untrusted text safe to print to an interactive terminal while preserving line breaks.
224
+ *
225
+ * @param {unknown} value console value
226
+ * @returns {string} inert console text
227
+ */
228
+ function sanitizeConsoleText (value) {
229
+ let result = ''
230
+ for (const character of sanitizeString(String(value ?? ''))) {
231
+ const code = character.charCodeAt(0)
232
+ result += character === '\n' || character === '\t' || code > 0x1F && code !== 0x7F
233
+ ? character
234
+ : String.raw`\u${code.toString(16).padStart(4, '0')}`
235
+ }
236
+ return result
237
+ }
238
+
239
+ /**
240
+ * Returns whether a key or variable name usually carries secret values.
241
+ *
242
+ * @param {string} name key or environment variable name
243
+ * @returns {boolean} true when values under this name should be redacted
244
+ */
245
+ function isSensitiveName (name) {
246
+ const normalized = String(name || '').replaceAll(/[-.]/g, '_')
247
+ return SENSITIVE_NAME_PATTERN.test(normalized) ||
248
+ SENSITIVE_AUTH_NAME_PATTERN.test(normalized) ||
249
+ SENSITIVE_COOKIE_NAME_PATTERN.test(normalized) ||
250
+ SENSITIVE_PASS_NAME_PATTERN.test(normalized) ||
251
+ SENSITIVE_TOKEN_ALIAS_NAME_PATTERN.test(normalized)
252
+ }
253
+
254
+ function sanitizeValue (value, seen, key, depth) {
255
+ if (typeof value === 'string') {
256
+ if (key && isSensitiveName(key) && !SECRET_NAME_ONLY_KEYS.has(key)) return REDACTED
257
+ return sanitizeString(value)
258
+ }
259
+
260
+ if (value === null || typeof value !== 'object') return value
261
+ if (depth > MAX_SANITIZE_DEPTH) return TRUNCATED_NESTING
262
+ if (seen.has(value)) return '[Circular]'
263
+ seen.add(value)
264
+
265
+ if (Array.isArray(value)) {
266
+ const sanitized = sanitizeArray(value, seen, key, depth)
267
+ seen.delete(value)
268
+ return sanitized
269
+ }
270
+
271
+ if (key && ENV_CONTAINER_KEYS.has(key)) {
272
+ const env = sanitizeEnv(value)
273
+ seen.delete(value)
274
+ return env || {}
275
+ }
276
+
277
+ const sanitized = {}
278
+ for (const [entryKey, entryValue] of Object.entries(value)) {
279
+ if (isSensitiveName(entryKey) && !SECRET_NAME_ONLY_KEYS.has(entryKey)) {
280
+ sanitized[entryKey] = REDACTED
281
+ continue
282
+ }
283
+ sanitized[entryKey] = sanitizeValue(entryValue, seen, entryKey, depth + 1)
284
+ }
285
+ seen.delete(value)
286
+ return sanitized
287
+ }
288
+
289
+ /**
290
+ * Redacts secret flag/value pairs in arrays such as argv before sanitizing nested values.
291
+ *
292
+ * @param {Array<unknown>} value array to sanitize
293
+ * @param {WeakSet<object>} seen objects currently being sanitized
294
+ * @param {string|undefined} key parent key
295
+ * @param {number} depth current nesting depth
296
+ * @returns {Array<unknown>} sanitized array
297
+ */
298
+ function sanitizeArray (value, seen, key, depth) {
299
+ const sanitized = []
300
+
301
+ for (let index = 0; index < value.length; index++) {
302
+ const item = value[index]
303
+ if (typeof item === 'string' && SECRET_FLAG_NAME_PATTERN.test(item) && index + 1 < value.length) {
304
+ sanitized.push(sanitizeString(item))
305
+ if (!isFlagToken(value[index + 1])) {
306
+ sanitized.push(REDACTED)
307
+ index++
308
+ }
309
+ continue
310
+ }
311
+
312
+ sanitized.push(sanitizeValue(item, seen, key, depth + 1))
313
+ }
314
+
315
+ return sanitized
316
+ }
317
+
318
+ function isFlagToken (value) {
319
+ return typeof value === 'string' && /^-{1,2}\S/.test(value)
320
+ }
321
+
322
+ module.exports = {
323
+ hasUnsafeExecutionCharacter,
324
+ hasUnsafeInvisibleCharacter,
325
+ isSensitiveName,
326
+ sanitizeConsoleText,
327
+ sanitizeEnv,
328
+ sanitizeEnvValue,
329
+ sanitizeForReport,
330
+ sanitizeString,
331
+ }