fix(ui): proxy canonical inbound media previews (#100725)

Co-authored-by: Cornna <96944678+ymylive@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-07-06 09:54:19 +01:00
committed by GitHub
parent 8b2e9ddc64
commit 3170bfa489
3 changed files with 175 additions and 0 deletions
+64
View File
@@ -346,6 +346,70 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
}
});
it("renders a canonical inbound image through the ticketed media route", async () => {
const context = await newBrowserContext({
locale: "en-US",
serviceWorkers: "block",
viewport: { height: 900, width: 1280 },
});
const page = await context.newPage();
const requestedMediaUrls: URL[] = [];
await page.route("**/__openclaw__/assistant-media?**", async (route) => {
const request = route.request();
const url = new URL(request.url());
requestedMediaUrls.push(url);
expect(url.searchParams.get("source")).toBe("media://inbound/telegram-photo.png");
if (url.searchParams.get("meta") === "1") {
expect(request.headers().authorization).toBe("Bearer e2e-device-token");
await route.fulfill({
contentType: "application/json",
body: JSON.stringify({
available: true,
mediaTicket: "ticket-inbound",
mediaTicketExpiresAt: new Date(Date.now() + 5 * 60_000).toISOString(),
}),
});
return;
}
expect(url.searchParams.get("mediaTicket")).toBe("ticket-inbound");
await route.fulfill({
contentType: "image/png",
body: Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9Zl1sAAAAASUVORK5CYII=",
"base64",
),
});
});
await installMockGateway(page, {
historyMessages: [
{
id: "user-inbound-media-ref",
role: "user",
content: [{ type: "text", text: "🖼️ Attached image" }],
MediaPath: "media://inbound/telegram-photo.png",
MediaType: "image/png",
timestamp: Date.now(),
},
],
});
try {
await page.goto(`${server.baseUrl}chat`);
await expect.poll(() => requestedMediaUrls.length, { timeout: 10_000 }).toBe(2);
const image = page.getByAltText("Attached image");
await image.waitFor({ state: "visible", timeout: 10_000 });
await expect
.poll(() =>
image.evaluate((element) =>
element instanceof HTMLImageElement && element.complete ? element.naturalWidth : 0,
),
)
.toBe(1);
} finally {
await closeBrowserContext(context);
}
});
it("opens current context and latest-run usage from the composer ring", async () => {
const context = await newBrowserContext({
locale: "en-US",
@@ -2076,6 +2076,94 @@ describe("grouped chat rendering", () => {
vi.unstubAllGlobals();
});
it("renders canonical inbound transcript images through the authenticated media route", async () => {
resetAssistantAttachmentAvailabilityCacheForTest();
const fetchMock = vi.fn(async (url: string, init?: RequestInit) => {
const mediaUrl = new URL(url, "http://control.test");
expect(mediaUrl.pathname).toBe("/openclaw/__openclaw__/assistant-media");
expect([...mediaUrl.searchParams.keys()].toSorted()).toEqual(["meta", "source"]);
expect(mediaUrl.searchParams.get("meta")).toBe("1");
expect(mediaUrl.searchParams.get("source")).toBe("media://inbound/telegram-photo.png");
expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer session-token");
return {
ok: true,
json: async () => mediaTicketPayload("ticket-inbound"),
};
});
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);
const container = document.createElement("div");
const renderMessage = () =>
renderGroupedMessage(
container,
{
id: "user-inbound-media-ref",
role: "user",
content: "",
MediaPath: "media://inbound/telegram-photo.png",
MediaType: "image/png",
timestamp: Date.now(),
},
"user",
{
showToolCalls: false,
basePath: "/openclaw",
assistantAttachmentAuthToken: "session-token",
localMediaPreviewRoots: [],
onRequestUpdate: renderMessage,
},
);
renderMessage();
await flushAssistantAttachmentAvailabilityChecks();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(
container.querySelector<HTMLImageElement>(".chat-message-image")?.getAttribute("src"),
).toBe(
"/openclaw/__openclaw__/assistant-media?source=media%3A%2F%2Finbound%2Ftelegram-photo.png&mediaTicket=ticket-inbound",
);
vi.unstubAllGlobals();
});
it.each([
"media://outbound/photo.png",
"media://inbound/",
"media://inbound/nested%2Fphoto.png",
"media://inbound/%00.png",
"media://inbound/nested/../photo.png",
"media://inbound/%2e%2e/photo.png",
"media://inbound/..",
"media://inbound/photo.png?raw=1",
"media://inbound/photo.png#preview",
])("does not proxy non-canonical inbound media ref %s", (source) => {
resetAssistantAttachmentAvailabilityCacheForTest();
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);
const container = document.createElement("div");
renderGroupedMessage(
container,
{
id: "user-invalid-inbound-media-ref",
role: "user",
content: "",
MediaPath: source,
MediaType: "image/png",
timestamp: Date.now(),
},
"user",
{
showToolCalls: false,
assistantAttachmentAuthToken: "session-token",
localMediaPreviewRoots: [],
},
);
expect(fetchMock).not.toHaveBeenCalled();
vi.unstubAllGlobals();
});
it("fetches managed chat images with auth and renders blob previews", async () => {
resetAssistantAttachmentAvailabilityCacheForTest();
const managedChatImageUrl =
@@ -1228,6 +1228,7 @@ function isLocalAssistantAttachmentSource(source: string): boolean {
return false;
}
return (
isCanonicalInboundMediaSource(trimmed) ||
trimmed.startsWith("file://") ||
trimmed.startsWith("~") ||
trimmed.startsWith("/") ||
@@ -1235,11 +1236,30 @@ function isLocalAssistantAttachmentSource(source: string): boolean {
);
}
function isCanonicalInboundMediaSource(source: string): boolean {
// Match the raw one-segment form first; URL parsing would erase dot segments.
const match = /^media:\/\/inbound\/([^/?#]+)$/i.exec(source.trim());
if (!match?.[1]) {
return false;
}
try {
const id = decodeURIComponent(match[1]);
return (
id !== "." && id !== ".." && !id.includes("/") && !id.includes("\\") && !id.includes("\0")
);
} catch {
return false;
}
}
function normalizeLocalAttachmentPath(source: string): string | null {
const trimmed = source.trim();
if (!isLocalAssistantAttachmentSource(trimmed)) {
return null;
}
if (isCanonicalInboundMediaSource(trimmed)) {
return null;
}
if (trimmed.startsWith("file://")) {
try {
const url = new URL(trimmed);
@@ -1290,6 +1310,9 @@ function isLocalAttachmentPreviewAllowed(
source: string,
localMediaPreviewRoots: readonly string[],
): boolean {
if (isCanonicalInboundMediaSource(source)) {
return true;
}
const normalizedSource = normalizeLocalAttachmentPath(source);
const comparableSources = normalizedSource
? [canonicalizeLocalPathForComparison(normalizedSource)]