From e32707458d14f22a016d5ba80e27cbf0484e8275 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 7 Jun 2026 04:37:41 +0200 Subject: [PATCH] fix(e2e): bound secret configure pty output --- scripts/e2e/secret-provider-integrations.mjs | 26 +++++--- .../secret-provider-integrations.test.ts | 60 +++++++++++++++++++ 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/scripts/e2e/secret-provider-integrations.mjs b/scripts/e2e/secret-provider-integrations.mjs index 1fb53ff9e613..a2b859f63e25 100644 --- a/scripts/e2e/secret-provider-integrations.mjs +++ b/scripts/e2e/secret-provider-integrations.mjs @@ -149,6 +149,12 @@ function createOutputCapture(label, options = {}) { text() { return output; }, + reset() { + output = ""; + bytes = 0; + truncated = false; + scanTail = ""; + }, leakedForbiddenValue() { return leakedForbiddenValue; }, @@ -1606,7 +1612,7 @@ async function runPtySecretsConfigurePreset(envCtx) { cwd: command.options.cwd ?? process.cwd(), env: command.options.env ?? envCtx.env, }); - let output = ""; + const output = createOutputCapture("secrets configure stdout"); let phase = "providers-menu"; const sendKeys = (keys) => { keys.forEach((key, index) => { @@ -1616,22 +1622,23 @@ async function runPtySecretsConfigurePreset(envCtx) { return await new Promise((resolve, reject) => { const timer = setTimeout(() => { child.kill(); - reject(new Error(`secrets configure preset timed out: ${scrub(output)}`)); + reject(new Error(`secrets configure preset timed out: ${scrub(output.text())}`)); }, 60000); child.onData((data) => { - output += data; - if (phase === "providers-menu" && output.includes("Configure secret providers")) { + output.append(data); + const outputText = output.text(); + if (phase === "providers-menu" && outputText.includes("Configure secret providers")) { phase = "selecting-preset"; sendKeys(["\x1b[B", "\r"]); return; } - if (phase === "selecting-preset" && output.includes("Select plugin preset")) { + if (phase === "selecting-preset" && outputText.includes("Select plugin preset")) { phase = "preset-selected"; sendKeys(["\r"]); - output = ""; + output.reset(); return; } - if (phase === "preset-selected" && output.includes("Configure secret providers")) { + if (phase === "preset-selected" && outputText.includes("Configure secret providers")) { phase = "continue-selected"; sendKeys(["\x1b[A", "\r"]); } @@ -1639,10 +1646,10 @@ async function runPtySecretsConfigurePreset(envCtx) { child.onExit(({ exitCode }) => { clearTimeout(timer); if (exitCode !== 0) { - reject(new Error(`secrets configure preset failed (${exitCode}): ${scrub(output)}`)); + reject(new Error(`secrets configure preset failed (${exitCode}): ${scrub(output.text())}`)); return; } - resolve(output); + resolve(output.text()); }); }); } @@ -1858,6 +1865,7 @@ export { cleanupEnv, expectGatewayStartupFails, gatewayCall, + runPtySecretsConfigurePreset, runCommand, startGateway, waitForManagedGatewayStatus, diff --git a/test/scripts/secret-provider-integrations.test.ts b/test/scripts/secret-provider-integrations.test.ts index 73276b5fdf44..1340c65c4f5b 100644 --- a/test/scripts/secret-provider-integrations.test.ts +++ b/test/scripts/secret-provider-integrations.test.ts @@ -114,6 +114,25 @@ function writeSignaledStartupOpenClaw(root: string): string { return scriptPath; } +function writeNoisySecretsConfigureOpenClaw(root: string): string { + const scriptPath = path.join(root, "fake-noisy-secrets-configure-openclaw.mjs"); + fs.writeFileSync( + scriptPath, + [ + "#!/usr/bin/env node", + "const args = process.argv.slice(2);", + "if (args[0] === 'secrets' && args[1] === 'configure') {", + " process.stdout.write('x'.repeat(4096));", + " process.exit(7);", + "}", + "process.exit(2);", + "", + ].join("\n"), + { mode: 0o755 }, + ); + return scriptPath; +} + function runProofHarness( root: string, fakeOpenClaw: string, @@ -237,6 +256,47 @@ describe("secret provider integration proof harness", () => { } }); + it.runIf(process.platform !== "win32")("bounds captured PTY configure output", async () => { + const root = makeTempDir(); + const fakeOpenClaw = writeNoisySecretsConfigureOpenClaw(root); + const previousLimit = process.env.OPENCLAW_SECRET_PROOF_OUTPUT_BYTES; + const previousEntry = process.env.OPENCLAW_ENTRY; + process.env.OPENCLAW_SECRET_PROOF_OUTPUT_BYTES = "128"; + process.env.OPENCLAW_ENTRY = fakeOpenClaw; + try { + const proof = await import( + `${pathToFileURL(proofScriptPath).href}?case=pty-output-${Date.now()}` + ); + + const error = await proof + .runPtySecretsConfigurePreset({ + env: { + ...process.env, + OPENCLAW_ENTRY: fakeOpenClaw, + }, + }) + .catch((caught: unknown) => caught); + + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toContain("secrets configure preset failed (7)"); + expect((error as Error).message).toContain( + "secrets configure stdout truncated after 128 bytes", + ); + expect((error as Error).message.length).toBeLessThan(600); + } finally { + if (previousLimit === undefined) { + delete process.env.OPENCLAW_SECRET_PROOF_OUTPUT_BYTES; + } else { + process.env.OPENCLAW_SECRET_PROOF_OUTPUT_BYTES = previousLimit; + } + if (previousEntry === undefined) { + delete process.env.OPENCLAW_ENTRY; + } else { + process.env.OPENCLAW_ENTRY = previousEntry; + } + } + }); + it.runIf(process.platform !== "win32")( "fails mandatory commands that exit by signal", async () => {