























1+// Qqbot tests cover trusted outbound media-path root resolution.
2+import fs from "node:fs";
3+import os from "node:os";
4+import path from "node:path";
5+import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
6+import { afterEach, describe, expect, it } from "vitest";
7+import { resolveOutboundMediaPath } from "./outbound-media-send.js";
8+import { resolveTrustedOutboundMediaPath } from "./trusted-media-path.js";
9+10+const cleanupPaths: string[] = [];
11+12+afterEach(() => {
13+while (cleanupPaths.length > 0) {
14+const target = cleanupPaths.pop();
15+if (target) {
16+fs.rmSync(target, { recursive: true, force: true });
17+}
18+}
19+});
20+21+function makeTtsStyleVoiceFile(): string {
22+// Mirrors cron auto-TTS: speech-core writes the voice file under the preferred
23+// OpenClaw temp root, which is outside the QQ Bot media storage tree.
24+const tmpRoot = resolvePreferredOpenClawTmpDir();
25+const ttsDir = fs.mkdtempSync(path.join(tmpRoot, "tts-"));
26+cleanupPaths.push(ttsDir);
27+const voicePath = path.join(ttsDir, "voice-123.mp3");
28+fs.writeFileSync(voicePath, "audio");
29+return voicePath;
30+}
31+32+describe("resolveTrustedOutboundMediaPath", () => {
33+it("trusts framework media under OpenClaw's hardened temp root", () => {
34+const voicePath = makeTtsStyleVoiceFile();
35+expect(resolveTrustedOutboundMediaPath(voicePath)).toBe(fs.realpathSync(voicePath));
36+});
37+38+it("rejects local media outside every trusted root", () => {
39+const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), "qq-out-of-root-"));
40+cleanupPaths.push(outsideDir);
41+const strayPath = path.join(outsideDir, "stray.mp3");
42+fs.writeFileSync(strayPath, "audio");
43+44+expect(resolveTrustedOutboundMediaPath(strayPath)).toBeNull();
45+});
46+47+it("accepts a not-yet-flushed temp file only when allowMissing is set", () => {
48+const tmpRoot = resolvePreferredOpenClawTmpDir();
49+const ttsDir = fs.mkdtempSync(path.join(tmpRoot, "tts-pending-"));
50+cleanupPaths.push(ttsDir);
51+const pendingPath = path.join(ttsDir, "voice-pending.mp3");
52+53+expect(resolveTrustedOutboundMediaPath(pendingPath)).toBeNull();
54+expect(resolveTrustedOutboundMediaPath(pendingPath, { allowMissing: true })).not.toBeNull();
55+});
56+});
57+58+describe("resolveOutboundMediaPath", () => {
59+it("resolves a cron/TTS voice file under the temp root end to end", () => {
60+// Both the initial resolve and the voice send re-check funnel through
61+// resolveTrustedOutboundMediaPath, so this gate now passes for temp media.
62+const voicePath = makeTtsStyleVoiceFile();
63+const resolved = resolveOutboundMediaPath(voicePath, "voice", {
64+allowMissingLocalPath: true,
65+});
66+67+expect(resolved.ok).toBe(true);
68+expect(resolved.ok && resolved.mediaPath).toBe(fs.realpathSync(voicePath));
69+});
70+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。