mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04:00
fix: enforce inbound attachment root policy across pipelines
This commit is contained in:
@@ -10,14 +10,19 @@ import {
|
||||
const sandboxMocks = vi.hoisted(() => ({
|
||||
ensureSandboxWorkspaceForSession: vi.fn(),
|
||||
}));
|
||||
const childProcessMocks = vi.hoisted(() => ({
|
||||
spawn: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/sandbox.js", () => sandboxMocks);
|
||||
vi.mock("node:child_process", () => childProcessMocks);
|
||||
|
||||
import { ensureSandboxWorkspaceForSession } from "../agents/sandbox.js";
|
||||
import { stageSandboxMedia } from "./reply/stage-sandbox-media.js";
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
childProcessMocks.spawn.mockReset();
|
||||
});
|
||||
|
||||
describe("stageSandboxMedia", () => {
|
||||
@@ -86,4 +91,31 @@ describe("stageSandboxMedia", () => {
|
||||
expect(ctx.MediaPath).toBe(sensitiveFile);
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks remote SCP staging for non-iMessage attachment paths", async () => {
|
||||
await withSandboxMediaTempHome("openclaw-triggers-remote-block-", async (home) => {
|
||||
const sandboxDir = join(home, "sandboxes", "session");
|
||||
vi.mocked(ensureSandboxWorkspaceForSession).mockResolvedValue({
|
||||
workspaceDir: sandboxDir,
|
||||
containerWorkdir: "/work",
|
||||
});
|
||||
|
||||
const { ctx, sessionCtx } = createSandboxMediaContexts("/etc/passwd");
|
||||
ctx.Provider = "imessage";
|
||||
ctx.MediaRemoteHost = "user@gateway-host";
|
||||
sessionCtx.Provider = "imessage";
|
||||
sessionCtx.MediaRemoteHost = "user@gateway-host";
|
||||
|
||||
await stageSandboxMedia({
|
||||
ctx,
|
||||
sessionCtx,
|
||||
cfg: createSandboxMediaStageConfig(home),
|
||||
sessionKey: "agent:main:main",
|
||||
workspaceDir: join(home, "openclaw"),
|
||||
});
|
||||
|
||||
expect(childProcessMocks.spawn).not.toHaveBeenCalled();
|
||||
expect(ctx.MediaPath).toBe("/etc/passwd");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,14 +2,18 @@ import { spawn } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||
import { assertSandboxPath } from "../../agents/sandbox-paths.js";
|
||||
import { ensureSandboxWorkspaceForSession } from "../../agents/sandbox.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { normalizeScpRemoteHost } from "../../infra/scp-host.js";
|
||||
import {
|
||||
isInboundPathAllowed,
|
||||
resolveIMessageRemoteAttachmentRoots,
|
||||
} from "../../media/inbound-path-policy.js";
|
||||
import { getMediaDir } from "../../media/store.js";
|
||||
import { CONFIG_DIR } from "../../utils.js";
|
||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||
|
||||
export async function stageSandboxMedia(params: {
|
||||
ctx: MsgContext;
|
||||
@@ -70,6 +74,10 @@ export async function stageSandboxMedia(params: {
|
||||
? path.join(effectiveWorkspaceDir, "media", "inbound")
|
||||
: effectiveWorkspaceDir;
|
||||
await fs.mkdir(destDir, { recursive: true });
|
||||
const remoteAttachmentRoots = resolveIMessageRemoteAttachmentRoots({
|
||||
cfg,
|
||||
accountId: ctx.AccountId,
|
||||
});
|
||||
|
||||
const usedNames = new Set<string>();
|
||||
const staged = new Map<string, string>(); // absolute source -> relative sandbox path
|
||||
@@ -83,9 +91,29 @@ export async function stageSandboxMedia(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
ctx.MediaRemoteHost &&
|
||||
!isInboundPathAllowed({
|
||||
filePath: source,
|
||||
roots: remoteAttachmentRoots,
|
||||
})
|
||||
) {
|
||||
logVerbose(`Blocking remote media staging from disallowed attachment path: ${source}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Local paths must be restricted to the media directory.
|
||||
if (!ctx.MediaRemoteHost) {
|
||||
const mediaDir = getMediaDir();
|
||||
if (
|
||||
!isInboundPathAllowed({
|
||||
filePath: source,
|
||||
roots: [mediaDir],
|
||||
})
|
||||
) {
|
||||
logVerbose(`Blocking attempt to stage media from outside media directory: ${source}`);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await assertSandboxPath({
|
||||
filePath: source,
|
||||
|
||||
Reference in New Issue
Block a user