























@@ -16,6 +16,28 @@ function makeTempDir(): string {
1616return root;
1717}
181819+async function waitFor(predicate: () => boolean, timeoutMs = 5_000): Promise<void> {
20+const started = Date.now();
21+while (Date.now() - started < timeoutMs) {
22+if (predicate()) {
23+return;
24+}
25+await new Promise((resolve) => {
26+setTimeout(resolve, 25);
27+});
28+}
29+throw new Error("condition was not met before timeout");
30+}
31+32+function isProcessAlive(pid: number): boolean {
33+try {
34+process.kill(pid, 0);
35+return true;
36+} catch {
37+return false;
38+}
39+}
40+1941function writeStallingOpenClaw(
2042root: string,
2143options: {
@@ -492,6 +514,80 @@ describe("secret provider integration proof harness", () => {
492514expect(sizeAfterWait).toBe(sizeAfterReturn);
493515});
494516517+it.runIf(process.platform !== "win32")(
518+"aborts command process groups after the leader exits before stdio closes",
519+async () => {
520+const root = makeTempDir();
521+const scriptPath = path.join(root, "leader-exits-stdout-held.mjs");
522+const descendantPidPath = path.join(root, "descendant.pid");
523+let descendantPid = 0;
524+fs.writeFileSync(
525+scriptPath,
526+[
527+"import childProcess from 'node:child_process';",
528+"import fs from 'node:fs';",
529+"const descendant = childProcess.spawn(process.execPath, [",
530+" '--input-type=module',",
531+" '--eval',",
532+" \"process.on('SIGTERM', () => process.exit(0)); setInterval(() => process.stdout.write('tick\\\\n'), 20);\",",
533+"], { stdio: ['ignore', 'inherit', 'ignore'] });",
534+`fs.writeFileSync(${JSON.stringify(descendantPidPath)}, String(descendant.pid));`,
535+"process.exit(0);",
536+"",
537+].join("\n"),
538+);
539+const proof = await import(`${pathToFileURL(proofScriptPath).href}?case=abort-${Date.now()}`);
540+const controller = new AbortController();
541+const command = proof.runCommand(process.execPath, [scriptPath], {
542+signal: controller.signal,
543+timeoutMs: 5_000,
544+});
545+546+try {
547+await waitFor(() => fs.existsSync(descendantPidPath));
548+descendantPid = Number.parseInt(fs.readFileSync(descendantPidPath, "utf8"), 10);
549+expect(Number.isInteger(descendantPid)).toBe(true);
550+expect(isProcessAlive(descendantPid)).toBe(true);
551+552+await new Promise((resolve) => {
553+setTimeout(resolve, 50);
554+});
555+controller.abort();
556+557+await expect(command).rejects.toThrow("command aborted");
558+await waitFor(() => !isProcessAlive(descendantPid));
559+} finally {
560+await command.catch(() => {});
561+if (descendantPid && isProcessAlive(descendantPid)) {
562+process.kill(descendantPid, "SIGKILL");
563+}
564+}
565+},
566+);
567+568+it.runIf(process.platform !== "win32")("aborts non-detached commands", async () => {
569+const proof = await import(
570+`${pathToFileURL(proofScriptPath).href}?case=abort-direct-${Date.now()}`
571+);
572+const controller = new AbortController();
573+const command = proof.runCommand(
574+process.execPath,
575+["--input-type=module", "--eval", "setInterval(() => {}, 1000);"],
576+{
577+detached: false,
578+signal: controller.signal,
579+timeoutMs: 5_000,
580+},
581+);
582+583+await new Promise((resolve) => {
584+setTimeout(resolve, 50);
585+});
586+controller.abort();
587+588+await expect(command).rejects.toThrow("command aborted");
589+});
590+495591it("detects startup secret leaks after the retained output cap", () => {
496592const root = makeTempDir();
497593const fakeOpenClaw = writeLeakingStartupOpenClaw(root);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。