diff --git a/src/node-host/invoke-system-run-plan.test.ts b/src/node-host/invoke-system-run-plan.test.ts index 3ecf7e517b5..9dad0a4aa22 100644 --- a/src/node-host/invoke-system-run-plan.test.ts +++ b/src/node-host/invoke-system-run-plan.test.ts @@ -167,20 +167,16 @@ function expectShellPayloadApprovalDenied(params: { if (process.platform === "win32") { return; } - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), params.tmpPrefix)); - try { - const scriptPath = path.join(tmp, params.fileName); - fs.writeFileSync(scriptPath, params.body); - fs.chmodSync(scriptPath, 0o755); - const prepared = buildSystemRunApprovalPlan({ - command: ["/bin/sh", "-lc", scriptPath], - rawCommand: scriptPath, - cwd: tmp, - }); - expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir(params.tmpPrefix); + const scriptPath = path.join(tmp, params.fileName); + fs.writeFileSync(scriptPath, params.body); + fs.chmodSync(scriptPath, 0o755); + const prepared = buildSystemRunApprovalPlan({ + command: ["/bin/sh", "-lc", scriptPath], + rawCommand: scriptPath, + cwd: tmp, + }); + expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); } function expectMutableFileOperandApprovalPlan(fixture: ScriptOperandFixture, cwd: string) { @@ -478,7 +474,7 @@ describe("hardenApprovedExecutionPaths", () => { ]; it.runIf(process.platform !== "win32").each(cases)("$name", (testCase) => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-approval-hardening-")); + const tmp = createFixtureDir("openclaw-approval-hardening-"); const oldPath = process.env.PATH; let pathToken: PathTokenSetup | null = null; if (testCase.withPathToken) { @@ -534,7 +530,6 @@ describe("hardenApprovedExecutionPaths", () => { process.env.PATH = oldPath; } } - fs.rmSync(tmp, { recursive: true, force: true }); } }); @@ -847,49 +842,41 @@ describe("hardenApprovedExecutionPaths", () => { if (process.platform === "win32") { return; } - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shell-relative-binary-binding-")); - try { - const binaryPath = resolveNativeBinaryFixturePath(); - const relativeBinaryPath = path.join(tmp, "tool"); - fs.copyFileSync(binaryPath, relativeBinaryPath); - fs.chmodSync(relativeBinaryPath, 0o755); - const prepared = buildSystemRunApprovalPlan({ - command: ["/bin/sh", "-lc", "./tool"], - rawCommand: "./tool", - cwd: tmp, - }); - expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-shell-relative-binary-binding-"); + const binaryPath = resolveNativeBinaryFixturePath(); + const relativeBinaryPath = path.join(tmp, "tool"); + fs.copyFileSync(binaryPath, relativeBinaryPath); + fs.chmodSync(relativeBinaryPath, 0o755); + const prepared = buildSystemRunApprovalPlan({ + command: ["/bin/sh", "-lc", "./tool"], + rawCommand: "./tool", + cwd: tmp, + }); + expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); }); it("keeps fail-closed behavior for writable absolute native-binary shell payloads", () => { if (process.platform === "win32") { return; } - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shell-absolute-binary-binding-")); - try { - const binaryPath = resolveNativeBinaryFixturePath(); - const copiedBinaryPath = path.join(tmp, "tool"); - fs.copyFileSync(binaryPath, copiedBinaryPath); - fs.chmodSync(copiedBinaryPath, 0o755); - const prepared = buildSystemRunApprovalPlan({ - command: ["/bin/sh", "-lc", copiedBinaryPath], - rawCommand: copiedBinaryPath, - cwd: tmp, - }); - expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-shell-absolute-binary-binding-"); + const binaryPath = resolveNativeBinaryFixturePath(); + const copiedBinaryPath = path.join(tmp, "tool"); + fs.copyFileSync(binaryPath, copiedBinaryPath); + fs.chmodSync(copiedBinaryPath, 0o755); + const prepared = buildSystemRunApprovalPlan({ + command: ["/bin/sh", "-lc", copiedBinaryPath], + rawCommand: copiedBinaryPath, + cwd: tmp, + }); + expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); }); it("keeps fail-closed behavior for owner-controlled read-only absolute binaries", () => { if (process.platform === "win32") { return; } - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shell-owned-readonly-binding-")); + const tmp = createFixtureDir("openclaw-shell-owned-readonly-binding-"); const binaryPath = path.join(tmp, "tool"); try { fs.copyFileSync(resolveNativeBinaryFixturePath(), binaryPath); @@ -903,7 +890,6 @@ describe("hardenApprovedExecutionPaths", () => { expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); } finally { fs.chmodSync(tmp, 0o755); - fs.rmSync(tmp, { recursive: true, force: true }); } }); @@ -911,7 +897,7 @@ describe("hardenApprovedExecutionPaths", () => { if (process.platform === "win32") { return; } - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shell-symlink-binary-binding-")); + const tmp = createFixtureDir("openclaw-shell-symlink-binary-binding-"); const stableDir = path.join(tmp, "stable"); const mutableDir = path.join(tmp, "mutable"); try { @@ -932,7 +918,6 @@ describe("hardenApprovedExecutionPaths", () => { expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); } finally { fs.chmodSync(stableDir, 0o755); - fs.rmSync(tmp, { recursive: true, force: true }); } }); @@ -972,35 +957,31 @@ describe("hardenApprovedExecutionPaths", () => { if (process.platform === "win32") { return; } - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shell-race-binding-")); - try { - const scriptPath = path.join(tmp, "run.sh"); - fs.writeFileSync(scriptPath, "#!/bin/sh\necho SAFE\n"); - fs.chmodSync(scriptPath, 0o755); - const realStatSync = fs.statSync; - let targetStatCalls = 0; - const statSyncSpy = vi.spyOn(fs, "statSync").mockImplementation((pathLike, options) => { - const targetPath = typeof pathLike === "string" ? pathLike : pathLike.toString(); - if (targetPath === scriptPath) { - targetStatCalls += 1; - if (targetStatCalls === 2) { - return realStatSync(tmp, options); - } + const tmp = createFixtureDir("openclaw-shell-race-binding-"); + const scriptPath = path.join(tmp, "run.sh"); + fs.writeFileSync(scriptPath, "#!/bin/sh\necho SAFE\n"); + fs.chmodSync(scriptPath, 0o755); + const realStatSync = fs.statSync; + let targetStatCalls = 0; + const statSyncSpy = vi.spyOn(fs, "statSync").mockImplementation((pathLike, options) => { + const targetPath = typeof pathLike === "string" ? pathLike : pathLike.toString(); + if (targetPath === scriptPath) { + targetStatCalls += 1; + if (targetStatCalls === 2) { + return realStatSync(tmp, options); } - return realStatSync(pathLike, options); - }); - try { - const prepared = buildSystemRunApprovalPlan({ - command: ["/bin/sh", "-lc", scriptPath], - rawCommand: scriptPath, - cwd: tmp, - }); - expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); - } finally { - statSyncSpy.mockRestore(); } + return realStatSync(pathLike, options); + }); + try { + const prepared = buildSystemRunApprovalPlan({ + command: ["/bin/sh", "-lc", scriptPath], + rawCommand: scriptPath, + cwd: tmp, + }); + expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + statSyncSpy.mockRestore(); } }); @@ -1008,13 +989,9 @@ describe("hardenApprovedExecutionPaths", () => { withFakeRuntimeBin({ binName: testCase.binName, run: () => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), testCase.tmpPrefix)); - try { - testCase.setup?.(tmp); - expectRuntimeApprovalDenied(testCase.command, tmp); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir(testCase.tmpPrefix); + testCase.setup?.(tmp); + expectRuntimeApprovalDenied(testCase.command, tmp); }, }); }); @@ -1062,19 +1039,15 @@ describe("hardenApprovedExecutionPaths", () => { withFakeRuntimeBins({ binNames: ["pnpm", "tsx"], run: () => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-pnpm-dlx-shell-mode-")); - try { - fs.writeFileSync(path.join(tmp, "run.ts"), 'console.log("SAFE");\n'); - expect( - resolveMutableFileOperandSnapshotSync({ - argv: ["pnpm", "dlx", "--shell-mode", "tsx ./run.ts"], - cwd: tmp, - shellCommand: null, - }), - ).toEqual({ ok: true, snapshot: null }); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-pnpm-dlx-shell-mode-"); + fs.writeFileSync(path.join(tmp, "run.ts"), 'console.log("SAFE");\n'); + expect( + resolveMutableFileOperandSnapshotSync({ + argv: ["pnpm", "dlx", "--shell-mode", "tsx ./run.ts"], + cwd: tmp, + shellCommand: null, + }), + ).toEqual({ ok: true, snapshot: null }); }, }); }); @@ -1083,12 +1056,8 @@ describe("hardenApprovedExecutionPaths", () => { withFakeRuntimeBin({ binName: "pnpm", run: () => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-pnpm-dlx-package-bin-")); - try { - expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "cowsay", "hello"], tmp); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-pnpm-dlx-package-bin-"); + expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "cowsay", "hello"], tmp); }, }); }); @@ -1097,14 +1066,8 @@ describe("hardenApprovedExecutionPaths", () => { withFakeRuntimeBin({ binName: "pnpm", run: () => { - const tmp = fs.mkdtempSync( - path.join(os.tmpdir(), "openclaw-pnpm-dlx-package-runtime-token-"), - ); - try { - expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "cowsay", "node"], tmp); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-pnpm-dlx-package-runtime-token-"); + expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "cowsay", "node"], tmp); }, }); }); @@ -1113,14 +1076,8 @@ describe("hardenApprovedExecutionPaths", () => { withFakeRuntimeBin({ binName: "pnpm", run: () => { - const tmp = fs.mkdtempSync( - path.join(os.tmpdir(), "openclaw-pnpm-dlx-package-runtime-token-multi-"), - ); - try { - expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "cowsay", "node", "hello"], tmp); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-pnpm-dlx-package-runtime-token-multi-"); + expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "cowsay", "node", "hello"], tmp); }, }); }); @@ -1129,14 +1086,10 @@ describe("hardenApprovedExecutionPaths", () => { withFakeRuntimeBins({ binNames: ["pnpm", "eslint"], run: () => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-pnpm-dlx-package-file-")); - try { - fs.mkdirSync(path.join(tmp, "src"), { recursive: true }); - fs.writeFileSync(path.join(tmp, "src", "index.ts"), 'console.log("SAFE");\n'); - expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "eslint", "src/index.ts"], tmp); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-pnpm-dlx-package-file-"); + fs.mkdirSync(path.join(tmp, "src"), { recursive: true }); + fs.writeFileSync(path.join(tmp, "src", "index.ts"), 'console.log("SAFE");\n'); + expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "eslint", "src/index.ts"], tmp); }, }); }); @@ -1145,16 +1098,9 @@ describe("hardenApprovedExecutionPaths", () => { withFakeRuntimeBin({ binName: "pnpm", run: () => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-pnpm-dlx-package-data-tail-")); - try { - fs.writeFileSync(path.join(tmp, "run.ts"), 'console.log("SAFE");\n'); - expectApprovalPlanWithoutMutableOperand( - ["pnpm", "dlx", "cowsay", "tsx", "./run.ts"], - tmp, - ); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-pnpm-dlx-package-data-tail-"); + fs.writeFileSync(path.join(tmp, "run.ts"), 'console.log("SAFE");\n'); + expectApprovalPlanWithoutMutableOperand(["pnpm", "dlx", "cowsay", "tsx", "./run.ts"], tmp); }, }); }); @@ -1183,26 +1129,22 @@ describe("hardenApprovedExecutionPaths", () => { }); it("captures the real shell script operand after value-taking shell flags", () => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shell-option-value-")); - try { - const scriptPath = path.join(tmp, "run.sh"); - fs.writeFileSync(scriptPath, "#!/bin/sh\necho SAFE\n"); - fs.writeFileSync(path.join(tmp, "errexit"), "decoy\n"); - const snapshot = resolveMutableFileOperandSnapshotSync({ - argv: ["/bin/bash", "-o", "errexit", "./run.sh"], - cwd: tmp, - shellCommand: null, - }); - expect(snapshot).toEqual({ - ok: true, - snapshot: { - argvIndex: 3, - path: fs.realpathSync(scriptPath), - sha256: expect.any(String), - }, - }); - } finally { - fs.rmSync(tmp, { recursive: true, force: true }); - } + const tmp = createFixtureDir("openclaw-shell-option-value-"); + const scriptPath = path.join(tmp, "run.sh"); + fs.writeFileSync(scriptPath, "#!/bin/sh\necho SAFE\n"); + fs.writeFileSync(path.join(tmp, "errexit"), "decoy\n"); + const snapshot = resolveMutableFileOperandSnapshotSync({ + argv: ["/bin/bash", "-o", "errexit", "./run.sh"], + cwd: tmp, + shellCommand: null, + }); + expect(snapshot).toEqual({ + ok: true, + snapshot: { + argvIndex: 3, + path: fs.realpathSync(scriptPath), + sha256: expect.any(String), + }, + }); }); }); diff --git a/src/plugin-activation-boundary.test.ts b/src/plugin-activation-boundary.test.ts index 6a28c606e9c..bfa6f24c19e 100644 --- a/src/plugin-activation-boundary.test.ts +++ b/src/plugin-activation-boundary.test.ts @@ -237,16 +237,7 @@ describe("plugin activation boundary", () => { ]); loadBundledPluginPublicSurfaceModuleSync.mockReset(); - const { getSessionBindingService } = - await import("./infra/outbound/session-binding-service.js"); - await expect(browser.closeTrackedBrowserTabsForSessions({ sessionKeys: [] })).resolves.toBe(0); - await expect( - getSessionBindingService().unbind({ - targetSessionKey: "agent:main:test", - reason: "session-reset", - }), - ).resolves.toEqual([]); expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); }); }); diff --git a/ui/src/ui/views/chat.test.ts b/ui/src/ui/views/chat.test.ts index 90eb7856974..d7acc580fc0 100644 --- a/ui/src/ui/views/chat.test.ts +++ b/ui/src/ui/views/chat.test.ts @@ -477,31 +477,6 @@ describe("chat view", () => { expect(container.textContent).not.toContain("Stop"); }); - it("shows sender labels from sanitized gateway messages instead of generic You", () => { - const container = document.createElement("div"); - render( - renderChat( - createProps({ - messages: [ - { - role: "user", - content: "hello from topic", - senderLabel: "Iris", - timestamp: 1000, - }, - ], - }), - ), - container, - ); - - const senderLabels = Array.from(container.querySelectorAll(".chat-sender-name")).map((node) => - node.textContent?.trim(), - ); - expect(senderLabels).toContain("Iris"); - expect(senderLabels).not.toContain("You"); - }); - it("keeps consecutive user messages from different senders in separate groups", () => { const container = document.createElement("div"); render( @@ -533,6 +508,7 @@ describe("chat view", () => { ); expect(senderLabels).toContain("Iris"); expect(senderLabels).toContain("Joaquin De Rojas"); + expect(senderLabels).not.toContain("You"); }); it("positions delete confirm by message side", () => { @@ -776,36 +752,7 @@ describe("chat view", () => { expect(container.textContent).toContain('"childSessionKey": "agent:test:subagent:abc123"'); }); - it("renders [embed] shortcodes inside the assistant bubble", () => { - const container = document.createElement("div"); - render( - renderChat( - createProps({ - showToolCalls: false, - messages: [ - { - id: "assistant-anki-inline", - role: "assistant", - content: [ - { - type: "text", - text: 'Still the same current card.\n[embed ref="cv_shortcode" title="Shortcode view" /]', - }, - ], - timestamp: Date.now(), - }, - ], - }), - ), - container, - ); - - expect(container.querySelector(".chat-tool-card__preview-frame")).not.toBeNull(); - expect(container.textContent).toContain("Still the same current card."); - expect(container.textContent).toContain("Shortcode view"); - }); - - it("renders canvas-only assistant bubbles", () => { + it("renders canvas-only [embed] shortcodes inside the assistant bubble", () => { const container = document.createElement("div"); render( renderChat( @@ -815,7 +762,12 @@ describe("chat view", () => { { id: "assistant-canvas-only", role: "assistant", - content: [{ type: "text", text: '[embed ref="cv_tictactoe" title="Tic-Tac-Toe" /]' }], + content: [ + { + type: "text", + text: '[embed ref="cv_tictactoe" title="Tic-Tac-Toe" /]', + }, + ], timestamp: Date.now(), }, ],