mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 13:31:25 +00:00
fix(security): lock sandbox tmp media paths to openclaw roots
This commit is contained in:
@@ -11,6 +11,7 @@ import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/c
|
||||
import { withEnvAsync } from "../../test-utils/env.js";
|
||||
import { createIMessageTestPlugin } from "../../test-utils/imessage-test-plugin.js";
|
||||
import { createInternalHookEventPayload } from "../../test-utils/internal-hook-event-payload.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../tmp-openclaw-dir.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
appendAssistantMessageToSessionTranscript: vi.fn(async () => ({ ok: true, sessionFile: "x" })),
|
||||
@@ -202,6 +203,86 @@ describe("deliverOutboundPayloads", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("includes OpenClaw tmp root in telegram mediaLocalRoots", async () => {
|
||||
const sendTelegram = vi.fn().mockResolvedValue({ messageId: "m1", chatId: "c1" });
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg: telegramChunkConfig,
|
||||
channel: "telegram",
|
||||
to: "123",
|
||||
payloads: [{ text: "hi", mediaUrl: "https://example.com/x.png" }],
|
||||
deps: { sendTelegram },
|
||||
});
|
||||
|
||||
expect(sendTelegram).toHaveBeenCalledWith(
|
||||
"123",
|
||||
"hi",
|
||||
expect.objectContaining({
|
||||
mediaLocalRoots: expect.arrayContaining([resolvePreferredOpenClawTmpDir()]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("includes OpenClaw tmp root in signal mediaLocalRoots", async () => {
|
||||
const sendSignal = vi.fn().mockResolvedValue({ messageId: "s1", timestamp: 123 });
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg: { channels: { signal: {} } },
|
||||
channel: "signal",
|
||||
to: "+1555",
|
||||
payloads: [{ text: "hi", mediaUrl: "https://example.com/x.png" }],
|
||||
deps: { sendSignal },
|
||||
});
|
||||
|
||||
expect(sendSignal).toHaveBeenCalledWith(
|
||||
"+1555",
|
||||
"hi",
|
||||
expect.objectContaining({
|
||||
mediaLocalRoots: expect.arrayContaining([resolvePreferredOpenClawTmpDir()]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("includes OpenClaw tmp root in whatsapp mediaLocalRoots", async () => {
|
||||
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "w1", toJid: "jid" });
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg: whatsappChunkConfig,
|
||||
channel: "whatsapp",
|
||||
to: "+1555",
|
||||
payloads: [{ text: "hi", mediaUrl: "https://example.com/x.png" }],
|
||||
deps: { sendWhatsApp },
|
||||
});
|
||||
|
||||
expect(sendWhatsApp).toHaveBeenCalledWith(
|
||||
"+1555",
|
||||
"hi",
|
||||
expect.objectContaining({
|
||||
mediaLocalRoots: expect.arrayContaining([resolvePreferredOpenClawTmpDir()]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("includes OpenClaw tmp root in imessage mediaLocalRoots", async () => {
|
||||
const sendIMessage = vi.fn().mockResolvedValue({ messageId: "i1", chatId: "chat-1" });
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg: {},
|
||||
channel: "imessage",
|
||||
to: "imessage:+15551234567",
|
||||
payloads: [{ text: "hi", mediaUrl: "https://example.com/x.png" }],
|
||||
deps: { sendIMessage },
|
||||
});
|
||||
|
||||
expect(sendIMessage).toHaveBeenCalledWith(
|
||||
"imessage:+15551234567",
|
||||
"hi",
|
||||
expect.objectContaining({
|
||||
mediaLocalRoots: expect.arrayContaining([resolvePreferredOpenClawTmpDir()]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses signal media maxBytes from config", async () => {
|
||||
const sendSignal = vi.fn().mockResolvedValue({ messageId: "s1", timestamp: 123 });
|
||||
const cfg: OpenClawConfig = { channels: { signal: { mediaMaxMb: 2 } } };
|
||||
|
||||
@@ -12,6 +12,7 @@ import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { createIMessageTestPlugin } from "../../test-utils/imessage-test-plugin.js";
|
||||
import { loadWebMedia } from "../../web/media.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../tmp-openclaw-dir.js";
|
||||
import { runMessageAction } from "./message-action-runner.js";
|
||||
|
||||
vi.mock("../../web/media.js", async () => {
|
||||
@@ -622,10 +623,12 @@ describe("runMessageAction sandboxed media validation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("allows media paths under os.tmpdir()", async () => {
|
||||
it("allows media paths under preferred OpenClaw tmp root", async () => {
|
||||
const tmpRoot = resolvePreferredOpenClawTmpDir();
|
||||
await fs.mkdir(tmpRoot, { recursive: true });
|
||||
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-sandbox-"));
|
||||
try {
|
||||
const tmpFile = path.join(os.tmpdir(), "test-media-image.png");
|
||||
const tmpFile = path.join(tmpRoot, "test-media-image.png");
|
||||
const result = await runMessageAction({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
@@ -644,6 +647,21 @@ describe("runMessageAction sandboxed media validation", () => {
|
||||
throw new Error("expected send result");
|
||||
}
|
||||
expect(result.sendResult?.mediaUrl).toBe(tmpFile);
|
||||
const hostTmpOutsideOpenClaw = path.join(os.tmpdir(), "outside-openclaw", "test-media.png");
|
||||
await expect(
|
||||
runMessageAction({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
media: hostTmpOutsideOpenClaw,
|
||||
message: "",
|
||||
},
|
||||
sandboxRoot: sandboxDir,
|
||||
dryRun: true,
|
||||
}),
|
||||
).rejects.toThrow(/sandbox/i);
|
||||
} finally {
|
||||
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user