























@@ -1,12 +1,19 @@
11import fs from "node:fs/promises";
22import os from "node:os";
33import path from "node:path";
4-import { createSandboxTestContext } from "openclaw/plugin-sdk/test-fixtures";
4+import type { CreateSandboxBackendParams } from "openclaw/plugin-sdk/sandbox";
5+import {
6+createSandboxBrowserConfig,
7+createSandboxPruneConfig,
8+createSandboxSshConfig,
9+createSandboxTestContext,
10+} from "openclaw/plugin-sdk/test-fixtures";
511import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
612import type { OpenShellSandboxBackend } from "./backend.js";
713import {
814applyGatewayEndpointToSshConfig,
915buildExecRemoteCommand,
16+buildValidatedExecRemoteCommand,
1017buildOpenShellBaseArgv,
1118resolveOpenShellCommand,
1219runOpenShellCli,
@@ -19,6 +26,7 @@ const cliMocks = vi.hoisted(() => ({
1926}));
20272128let createOpenShellSandboxBackendManager: typeof import("./backend.js").createOpenShellSandboxBackendManager;
29+let createOpenShellSandboxBackendFactory: typeof import("./backend.js").createOpenShellSandboxBackendFactory;
22302331describe("openshell cli helpers", () => {
2432const originalEnv = { ...process.env };
@@ -77,6 +85,15 @@ describe("openshell cli helpers", () => {
7785expect(command).toContain(`'cd '"'"'/sandbox/project'"'"' && pwd && printenv TOKEN'`);
7886});
798788+it("uses the shared SSH exec command preflight", () => {
89+expect(() =>
90+buildValidatedExecRemoteCommand({
91+command: 'workflow run <workflow-id> "<task>"',
92+env: {},
93+}),
94+).toThrow(/unresolved placeholder token <workflow-id>/);
95+});
96+8097it("passes direct gateway endpoints to openshell commands without registration", async () => {
8198const calls: string[][] = [];
8299const openshellCommand = await makeExecutable({
@@ -151,7 +168,8 @@ describe("openshell backend manager", () => {
151168runOpenShellCli: cliMocks.runOpenShellCli,
152169};
153170});
154-({ createOpenShellSandboxBackendManager } = await import("./backend.js"));
171+({ createOpenShellSandboxBackendFactory, createOpenShellSandboxBackendManager } =
172+await import("./backend.js"));
155173});
156174157175afterAll(() => {
@@ -261,10 +279,59 @@ describe("openshell backend manager", () => {
261279args: ["sandbox", "delete", "openclaw-session-5678"],
262280});
263281});
282+283+it("rejects malformed exec commands before opening an OpenShell SSH session", async () => {
284+const factory = createOpenShellSandboxBackendFactory({
285+pluginConfig: resolveOpenShellPluginConfig({
286+command: "openshell",
287+}),
288+});
289+const backend = await factory({
290+sessionKey: "agent:main:turn",
291+scopeKey: "agent:main",
292+workspaceDir: "/tmp/workspace",
293+agentWorkspaceDir: "/tmp/workspace",
294+cfg: createOpenShellBackendSandboxConfig(),
295+});
296+297+await expect(
298+backend.buildExecSpec({
299+command: "workflow install <name>",
300+env: {},
301+usePty: false,
302+}),
303+).rejects.toThrow(/unresolved placeholder token <name>/);
304+expect(cliMocks.runOpenShellCli).not.toHaveBeenCalled();
305+});
264306});
265307266308const tempDirs: string[] = [];
267309310+function createOpenShellBackendSandboxConfig(): CreateSandboxBackendParams["cfg"] {
311+return {
312+mode: "all",
313+backend: "openshell",
314+scope: "session",
315+workspaceAccess: "rw",
316+workspaceRoot: "/tmp/openclaw-sandboxes",
317+docker: {
318+image: "openclaw-sandbox:bookworm-slim",
319+containerPrefix: "openclaw-sbx-",
320+workdir: "/workspace",
321+readOnlyRoot: false,
322+tmpfs: [],
323+network: "none",
324+capDrop: [],
325+binds: [],
326+env: {},
327+},
328+ssh: createSandboxSshConfig("/tmp/openclaw-sandboxes"),
329+browser: createSandboxBrowserConfig(),
330+tools: { allow: ["*"], deny: [] },
331+prune: createSandboxPruneConfig(),
332+};
333+}
334+268335async function makeTempDir(prefix: string) {
269336const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
270337tempDirs.push(dir);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。