dd-trace 6.4.0 → 6.6.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.
- package/README.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +11 -8
- package/packages/datadog-instrumentations/src/ai.js +8 -2
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +31 -19
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/jest.js +44 -17
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +79 -32
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +162 -46
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +18 -8
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +3 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +14 -28
- package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
- package/packages/datadog-plugin-cucumber/src/index.js +3 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +87 -45
- package/packages/datadog-plugin-graphql/src/validate.js +2 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +2 -1
- package/packages/datadog-plugin-playwright/src/index.js +181 -65
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-vitest/src/index.js +5 -5
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +66 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +10 -3
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +68 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +16 -27
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +56 -4
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
- package/packages/dd-trace/src/proxy.js +84 -7
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -14
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- 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
|
+
}
|