fix(security): restrict MEDIA path extraction to prevent LFI (#4930)

* fix(security): restrict inbound media staging to media directory

* docs: update MEDIA path guidance for security restrictions

- Update agent hint to warn against absolute/~ paths
- Update docs example to use https:// instead of /tmp/

---------

Co-authored-by: Evan Otero <evanotero@google.com>
This commit is contained in:
Glucksberg
2026-01-31 14:55:37 -04:00
committed by GitHub
parent f1de88c198
commit 34e2425b4d
4 changed files with 98 additions and 2 deletions

View File

@@ -248,7 +248,7 @@ export async function runPreparedReply(
const prefixedBody = [threadStarterNote, prefixedBodyBase].filter(Boolean).join("\n\n");
const mediaNote = buildInboundMediaNote(ctx);
const mediaReplyHint = mediaNote
? "To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:/path or MEDIA:https://example.com/image.jpg (spaces ok, quote if needed). Keep caption in the text body."
? "To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:https://example.com/image.jpg (spaces ok, quote if needed) or a safe relative path like MEDIA:./image.jpg. Avoid absolute paths (MEDIA:/...) and ~ paths — they are blocked for security. Keep caption in the text body."
: undefined;
let prefixedCommandBody = mediaNote
? [mediaNote, mediaReplyHint, prefixedBody ?? ""].filter(Boolean).join("\n").trim()

View File

@@ -2,9 +2,11 @@ import { spawn } from "node:child_process";
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
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 { getMediaDir } from "../../media/store.js";
import { CONFIG_DIR } from "../../utils.js";
import type { MsgContext, TemplateContext } from "../templating.js";
@@ -80,6 +82,21 @@ export async function stageSandboxMedia(params: {
continue;
}
// Local paths must be restricted to the media directory.
if (!ctx.MediaRemoteHost) {
const mediaDir = getMediaDir();
try {
await assertSandboxPath({
filePath: source,
cwd: mediaDir,
root: mediaDir,
});
} catch {
logVerbose(`Blocking attempt to stage media from outside media directory: ${source}`);
continue;
}
}
const baseName = path.basename(source);
if (!baseName) {
continue;