fix: enforce inbound attachment root policy across pipelines

This commit is contained in:
Peter Steinberger
2026-02-19 14:15:34 +01:00
parent cfe8457a0f
commit 1316e57403
16 changed files with 555 additions and 37 deletions

View File

@@ -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,