From f6424aa484437b8eef3a34dec0f6315ab9a45475 Mon Sep 17 00:00:00 2001 From: jet Date: Thu, 9 Apr 2026 11:06:43 +0800 Subject: [PATCH] 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 --- src/auto-reply/reply/reply-media-paths.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/auto-reply/reply/reply-media-paths.ts b/src/auto-reply/reply/reply-media-paths.ts index 8fc57b9698c..1e213f3dd92 100644 --- a/src/auto-reply/reply/reply-media-paths.ts +++ b/src/auto-reply/reply/reply-media-paths.ts @@ -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) ||