From 4911be70ba81a996c8ca9701c5e9897c77c22a47 Mon Sep 17 00:00:00 2001 From: Jason Morse Date: Tue, 27 Oct 2026 15:32:13 -0700 Subject: [PATCH] fix(desktop-test): do not redact short secret-named environment values NodeDesktopCommandRunner registered the value of every environment variable named like a token, secret, password, nonce, or authorization header as a secret. A value such as `true` then rewrote machine-readable output (`"delegated":true` became `"delegated":[REDACTED]`). That fails the delegated-cli contract in the Office main pipeline, where the environment carries such a variable, and stops the stages after the tests, including Component Governance scanning. Register only values of at least 8 characters. Explicit addSecret calls are unchanged. The new command-secrets contract covers a flag, a 7-character value, and an 8-character value, and fails without the change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b6f34858-06bf-4230-8be7-1f65402b208a --- .changeset/desktop-test-env-secret-length.md | 5 ++++ .../desktop-test/references/reporting.md | 8 +++++++ packages/agentic/desktop-test/src/commands.ts | 7 +++++- .../desktop-test/src/contracts.contract.cjs | 23 +++++++++++++++++++ .../desktop-test/src/contracts.test.ts | 1 + 5 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 .changeset/desktop-test-env-secret-length.md diff --git a/.changeset/desktop-test-env-secret-length.md b/.changeset/desktop-test-env-secret-length.md new file mode 100644 index 0000000000..d78376863a --- /dev/null +++ b/.changeset/desktop-test-env-secret-length.md @@ -0,0 +1,5 @@ +--- +'@fluentui-react-native/desktop-test': patch +--- + +Stop redacting short environment values such as `true` or `0` from command output. Variables named like a token, secret, password, nonce, or authorization header are only registered as secrets when their value is at least 8 characters long, so machine-readable output like `"delegated":true` is no longer rewritten to `[REDACTED]`. diff --git a/packages/agentic/desktop-test/references/reporting.md b/packages/agentic/desktop-test/references/reporting.md index 5b485a9662..ee4c7f19a5 100644 --- a/packages/agentic/desktop-test/references/reporting.md +++ b/packages/agentic/desktop-test/references/reporting.md @@ -16,6 +16,14 @@ Each run writes `artifacts/desktop//` by default: | `junit.xml` | Test outcomes, infrastructure errors, and incomplete work | | `commands/` | Bounded, redacted stdout/stderr logs with source attribution | +Command output is redacted before it is logged or reported. Redaction covers the +secrets the harness registers and the values of environment variables whose +names contain `nonce`, `token`, `password`, `secret`, or `authorization`, when +the value is at least 8 characters long. Shorter values such as `true` or `0` +are flags, not credentials, and redacting them would rewrite machine-readable +output. Register a shorter credential explicitly with the reporter's +`addSecret`. + Stderr and test progress are live without verbose mode. Failed commands replay the collected output. GitHub Actions grouping and annotations are rendered by the supervisor; child output cannot issue workflow commands. Upload the full diff --git a/packages/agentic/desktop-test/src/commands.ts b/packages/agentic/desktop-test/src/commands.ts index fdbc0ac0e5..3222fc6cf6 100644 --- a/packages/agentic/desktop-test/src/commands.ts +++ b/packages/agentic/desktop-test/src/commands.ts @@ -39,6 +39,10 @@ export type NodeDesktopCommandRunnerOptions = { maxLogBytes?: number; }; +// Credentials are long. Flags and counters such as `true` or `0` are not, and redacting them rewrites machine-readable +// output (`"delegated":true` becomes `"delegated":[REDACTED]`). Shorter secrets must be registered explicitly with addSecret. +const MIN_ENVIRONMENT_SECRET_LENGTH = 8; + export class NodeDesktopCommandRunner implements DesktopCommandRunner { private reporterValue?: DesktopReportWriter; private ownedReporter?: DesktopRunReporter; @@ -84,7 +88,8 @@ export class NodeDesktopCommandRunner implements DesktopCommandRunner { verbose: this.options.verbose, })); for (const [key, value] of Object.entries({ ...process.env, ...command.env, ...this.options.environment })) { - if (/nonce|token|password|secret|authorization/i.test(key) && value) reporter.addSecret(value); + if (/nonce|token|password|secret|authorization/i.test(key) && value && value.length >= MIN_ENVIRONMENT_SECRET_LENGTH) + reporter.addSecret(value); } const id = `command-${randomUUID()}`; const emit = reporter.source(id); diff --git a/packages/agentic/desktop-test/src/contracts.contract.cjs b/packages/agentic/desktop-test/src/contracts.contract.cjs index 1ab14c4c48..0a891466b8 100644 --- a/packages/agentic/desktop-test/src/contracts.contract.cjs +++ b/packages/agentic/desktop-test/src/contracts.contract.cjs @@ -323,6 +323,29 @@ async function main() { .slice(out.text().indexOf('::group::one'), out.text().indexOf('::endgroup::', out.text().indexOf('::group::one'))); assert(groupOne.includes('::stop-commands::')); assert(!groupOne.includes('::warning::two')); + } else if (mode === 'command-secrets') { + const run = reporter({ kind: 'lifecycle' }); + const runner = new NodeDesktopCommandRunner({ reporter: run }); + // Secret-named variables with short values (flags, counters) must not rewrite machine output; 8 characters and up are redacted. + await runner.run({ + ...command( + "console.log(JSON.stringify({ delegated: true, exitCode: 0 })); console.log('seven ' + process.env.FIXTURE_SEVEN_NONCE); console.log('eight ' + process.env.FIXTURE_EIGHT_SECRET);", + 'secret-environment', + ), + env: { FIXTURE_FLAG_TOKEN: 'true', FIXTURE_SEVEN_NONCE: '1234567', FIXTURE_EIGHT_SECRET: 'abcdefgh' }, + }); + assert.equal((await run.finish()).status, 'passed'); + const output = fs + .readFileSync(path.join(run.root, 'events.jsonl'), 'utf8') + .trim() + .split('\n') + .map((line) => JSON.parse(line)) + .filter((event) => event.kind === 'output' && event.payload.stream === 'stdout') + .map((event) => event.payload.text); + assert.deepEqual(JSON.parse(output.find((text) => text.includes('"delegated"'))), { delegated: true, exitCode: 0 }); + assert(output.includes('seven 1234567\n'), JSON.stringify(output)); + assert(output.includes('eight [REDACTED]\n'), JSON.stringify(output)); + assert(!output.join('').includes('abcdefgh')); } else if (mode === 'transport') { const { startReportTransport } = await import(lib('reporting/transport.js')); const run = reporter({ kind: 'lifecycle' }); diff --git a/packages/agentic/desktop-test/src/contracts.test.ts b/packages/agentic/desktop-test/src/contracts.test.ts index 6702f2fcae..fe2f97b348 100644 --- a/packages/agentic/desktop-test/src/contracts.test.ts +++ b/packages/agentic/desktop-test/src/contracts.test.ts @@ -7,6 +7,7 @@ describe('neutral desktop harness contracts', () => { 'adapters', 'reporting', 'commands', + 'command-secrets', 'transport', 'delegated-cli', 'services',