fix(tts): allow OpenClaw temp directory paths in reply media normalizer

TTS output files are written to /tmp/openclaw/tts-*/voice-*.opus by
resolvePreferredOpenClawTmpDir(), but the reply-media-path normalizer
only allows managed global paths and workspace volatile paths.
Absolute paths under /tmp/openclaw/ are blocked with 'Absolute host-local
MEDIA paths are blocked in normal replies', causing TTS audio attachments
to be silently dropped before delivery.

Add isOpenClawTmpPath() check to isAllowedAbsoluteReplyMediaPath() so
that files in the OpenClaw temp directory are treated as trusted.

Closes #63110
This commit is contained in:
jet
2026-04-09 11:06:43 +08:00
committed by George Pickett
parent 0362f21784
commit f6424aa484

View File

@@ -7,6 +7,7 @@ import { ensureSandboxWorkspaceForSession } from "../../agents/sandbox.js";
import { resolveEffectiveToolFsWorkspaceOnly } from "../../agents/tool-fs-policy.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { logVerbose } from "../../globals.js";
import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js";
import { saveMediaSource } from "../../media/store.js";
import { resolveConfigDir } from "../../utils.js";
import type { ReplyPayload } from "../types.js";
@@ -42,12 +43,32 @@ function isAllowedAbsoluteReplyMediaPath(params: {
if (isManagedGlobalReplyMediaPath(params.candidate)) {
return true;
}
// Allow media from the OpenClaw temp directory (TTS output, etc.).
// These are trusted paths written by OpenClaw's own tooling
// and should be deliverable as reply media.
if (isOpenClawTmpPath(params.candidate)) {
return true;
}
const volatileRoots = [params.workspaceDir, params.sandboxRoot]
.filter((root): root is string => Boolean(root))
.map((root) => path.join(path.resolve(root), AGENT_STATE_MEDIA_DIRNAME));
return volatileRoots.some((root) => isPathInside(root, params.candidate));
}
/**
* Check whether a path is inside the OpenClaw temp directory.
* These are trusted paths written by OpenClaw's own tooling
* (TTS, media processing, etc.) and should be deliverable as reply media.
*/
function isOpenClawTmpPath(candidate: string): boolean {
try {
const tmpRoot = resolvePreferredOpenClawTmpDir();
return isPathInside(tmpRoot, candidate);
} catch {
return false;
}
}
function isLikelyLocalMediaSource(media: string): boolean {
return (
FILE_URL_RE.test(media) ||