mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 01:44:33 +00:00
fix: execute sandboxed file ops inside containers (#4026)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 795ec6aa2f
Co-authored-by: davidbors-snyk <240482518+davidbors-snyk@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
This commit is contained in:
88
src/agents/sandbox/fs-bridge.test.ts
Normal file
88
src/agents/sandbox/fs-bridge.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("./docker.js", () => ({
|
||||
execDockerRaw: vi.fn(),
|
||||
}));
|
||||
|
||||
import type { SandboxContext } from "./types.js";
|
||||
import { execDockerRaw } from "./docker.js";
|
||||
import { createSandboxFsBridge } from "./fs-bridge.js";
|
||||
|
||||
const mockedExecDockerRaw = vi.mocked(execDockerRaw);
|
||||
|
||||
const sandbox: SandboxContext = {
|
||||
enabled: true,
|
||||
sessionKey: "sandbox:test",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
agentWorkspaceDir: "/tmp/workspace",
|
||||
workspaceAccess: "rw",
|
||||
containerName: "moltbot-sbx-test",
|
||||
containerWorkdir: "/workspace",
|
||||
docker: {
|
||||
image: "moltbot-sandbox:bookworm-slim",
|
||||
containerPrefix: "moltbot-sbx-",
|
||||
network: "none",
|
||||
user: "1000:1000",
|
||||
workdir: "/workspace",
|
||||
readOnlyRoot: false,
|
||||
tmpfs: [],
|
||||
capDrop: [],
|
||||
seccompProfile: "",
|
||||
apparmorProfile: "",
|
||||
setupCommand: "",
|
||||
binds: [],
|
||||
dns: [],
|
||||
extraHosts: [],
|
||||
pidsLimit: 0,
|
||||
},
|
||||
tools: { allow: ["*"], deny: [] },
|
||||
browserAllowHostControl: false,
|
||||
};
|
||||
|
||||
describe("sandbox fs bridge shell compatibility", () => {
|
||||
beforeEach(() => {
|
||||
mockedExecDockerRaw.mockReset();
|
||||
mockedExecDockerRaw.mockImplementation(async (args) => {
|
||||
const script = args[5] ?? "";
|
||||
if (script.includes('stat -c "%F|%s|%Y"')) {
|
||||
return {
|
||||
stdout: Buffer.from("regular file|1|2"),
|
||||
stderr: Buffer.alloc(0),
|
||||
code: 0,
|
||||
};
|
||||
}
|
||||
if (script.includes('cat -- "$1"')) {
|
||||
return {
|
||||
stdout: Buffer.from("content"),
|
||||
stderr: Buffer.alloc(0),
|
||||
code: 0,
|
||||
};
|
||||
}
|
||||
return {
|
||||
stdout: Buffer.alloc(0),
|
||||
stderr: Buffer.alloc(0),
|
||||
code: 0,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it("uses POSIX-safe shell prologue in all bridge commands", async () => {
|
||||
const bridge = createSandboxFsBridge({ sandbox });
|
||||
|
||||
await bridge.readFile({ filePath: "a.txt" });
|
||||
await bridge.writeFile({ filePath: "b.txt", data: "hello" });
|
||||
await bridge.mkdirp({ filePath: "nested" });
|
||||
await bridge.remove({ filePath: "b.txt" });
|
||||
await bridge.rename({ from: "a.txt", to: "c.txt" });
|
||||
await bridge.stat({ filePath: "c.txt" });
|
||||
|
||||
expect(mockedExecDockerRaw).toHaveBeenCalled();
|
||||
|
||||
const scripts = mockedExecDockerRaw.mock.calls.map(([args]) => args[5] ?? "");
|
||||
const executables = mockedExecDockerRaw.mock.calls.map(([args]) => args[3] ?? "");
|
||||
|
||||
expect(executables.every((shell) => shell === "sh")).toBe(true);
|
||||
expect(scripts.every((script) => script.includes("set -eu;"))).toBe(true);
|
||||
expect(scripts.some((script) => script.includes("pipefail"))).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user