Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/service/attachment-uploader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,31 @@ describe("attachment uploader", () => {
expect(attachmentsApi.startMultiPartUpload).not.toHaveBeenCalled();
});

test("ignores OpenClaw media:// authority URIs without attempting an upload or warning", async () => {
const attachmentsApi = createAttachmentsApi();
const client = { api: { attachments: attachmentsApi } };
const onWarn = vi.fn();

const uploader = createAttachmentUploader({
getClient: () => client as unknown as Opik,
getAttachmentBaseUrl: () => "https://www.comet.com/opik/api",
onWarn,
formatError: (err) => String(err),
});

uploader.scheduleMediaAttachmentUploads({
entityType: "trace",
entity: { id: "trace-1" },
projectName: "openclaw",
reason: "media-authority-uri",
payloads: ["media://inbound/example.jpg"],
});
await uploader.waitForUploads();

expect(attachmentsApi.startMultiPartUpload).not.toHaveBeenCalled();
expect(onWarn).not.toHaveBeenCalled();
});

test("uploads multipart attachments without loading the whole file into one request body", async () => {
const largeContents = Buffer.alloc(ATTACHMENT_UPLOAD_PART_SIZE_BYTES + 32, 0x61);
const { dir, filePath } = await createTempMediaFile(".png", largeContents);
Expand Down
12 changes: 12 additions & 0 deletions src/service/media.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ describe("media path extraction", () => {
expect([...target]).toEqual(["/tmp/image.png"]);
});

test("ignores media:// authority URIs instead of normalizing them to local paths", () => {
const target = new Set<string>();
collectMediaPathsFromString("media://inbound/example.jpg", target);
expect(target.size).toBe(0);
});

test("ignores media:// authority URIs with a host segment", () => {
const target = new Set<string>();
collectMediaPathsFromString("see media://host/path/to/example.jpg now", target);
expect(target.size).toBe(0);
});

test("collects file:// local path references", () => {
const target = new Set<string>();
collectMediaPathsFromString("open file:///tmp/image.png", target);
Expand Down
4 changes: 3 additions & 1 deletion src/service/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ const MEDIA_EXTENSIONS = new Set([
".mkv",
]);

// Reject the media:// authority form (e.g. OpenClaw media://inbound/...) — those are
// logical URIs, not local paths, and must not be normalized to /inbound/... and stat'd.
const MEDIA_SCHEME_LOCAL_PATH_RE =
/\bmedia:((?:~\/|\/)[^\s"'`]+?\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif|svg|mp3|wav|m4a|aac|ogg|oga|flac|opus|caf|weba|webm|mp4|mov|mkv))(?=[\s"'`]|$)/gi;
/\bmedia:(?!\/\/)((?:~\/|\/)[^\s"'`]+?\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif|svg|mp3|wav|m4a|aac|ogg|oga|flac|opus|caf|weba|webm|mp4|mov|mkv))(?=[\s"'`]|$)/gi;

const FILE_SCHEME_LOCAL_PATH_RE =
/\bfile:\/\/((?:~\/|\/)[^\s"'`]+?\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif|svg|mp3|wav|m4a|aac|ogg|oga|flac|opus|caf|weba|webm|mp4|mov|mkv))(?:\?[^\s"'`]*)?(?=[\s"'`]|$)/gi;
Expand Down