fix(coding-agent): enforce safe auto-resized image limits closes #2055
This commit is contained in:
@@ -52,12 +52,13 @@ describe("resizeImage", () => {
|
||||
{ maxWidth: 100, maxHeight: 100, maxBytes: 1024 * 1024 },
|
||||
);
|
||||
|
||||
expect(result.wasResized).toBe(false);
|
||||
expect(result.data).toBe(TINY_PNG);
|
||||
expect(result.originalWidth).toBe(2);
|
||||
expect(result.originalHeight).toBe(2);
|
||||
expect(result.width).toBe(2);
|
||||
expect(result.height).toBe(2);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.wasResized).toBe(false);
|
||||
expect(result!.data).toBe(TINY_PNG);
|
||||
expect(result!.originalWidth).toBe(2);
|
||||
expect(result!.originalHeight).toBe(2);
|
||||
expect(result!.width).toBe(2);
|
||||
expect(result!.height).toBe(2);
|
||||
});
|
||||
|
||||
it("should resize image exceeding dimension limits", async () => {
|
||||
@@ -66,26 +67,38 @@ describe("resizeImage", () => {
|
||||
{ maxWidth: 50, maxHeight: 50, maxBytes: 1024 * 1024 },
|
||||
);
|
||||
|
||||
expect(result.wasResized).toBe(true);
|
||||
expect(result.originalWidth).toBe(100);
|
||||
expect(result.originalHeight).toBe(100);
|
||||
expect(result.width).toBeLessThanOrEqual(50);
|
||||
expect(result.height).toBeLessThanOrEqual(50);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.wasResized).toBe(true);
|
||||
expect(result!.originalWidth).toBe(100);
|
||||
expect(result!.originalHeight).toBe(100);
|
||||
expect(result!.width).toBeLessThanOrEqual(50);
|
||||
expect(result!.height).toBeLessThanOrEqual(50);
|
||||
});
|
||||
|
||||
it("should resize image exceeding byte limit", async () => {
|
||||
const originalBuffer = Buffer.from(LARGE_PNG_200x200, "base64");
|
||||
const originalSize = originalBuffer.length;
|
||||
|
||||
// Set maxBytes to less than the original image size
|
||||
// Set maxBytes to less than the original encoded image size
|
||||
const result = await resizeImage(
|
||||
{ type: "image", data: LARGE_PNG_200x200, mimeType: "image/png" },
|
||||
{ maxWidth: 2000, maxHeight: 2000, maxBytes: Math.floor(originalSize / 2) },
|
||||
{ maxWidth: 2000, maxHeight: 2000, maxBytes: Math.floor(LARGE_PNG_200x200.length * 0.9) },
|
||||
);
|
||||
|
||||
// Should have tried to reduce size
|
||||
const resultBuffer = Buffer.from(result.data, "base64");
|
||||
expect(result).not.toBeNull();
|
||||
const resultBuffer = Buffer.from(result!.data, "base64");
|
||||
expect(resultBuffer.length).toBeLessThan(originalSize);
|
||||
expect(result!.data.length).toBeLessThan(LARGE_PNG_200x200.length);
|
||||
});
|
||||
|
||||
it("should return null when image cannot be resized below maxBytes", async () => {
|
||||
const result = await resizeImage(
|
||||
{ type: "image", data: LARGE_PNG_200x200, mimeType: "image/png" },
|
||||
{ maxWidth: 2000, maxHeight: 2000, maxBytes: 1 },
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle JPEG input", async () => {
|
||||
@@ -94,9 +107,10 @@ describe("resizeImage", () => {
|
||||
{ maxWidth: 100, maxHeight: 100, maxBytes: 1024 * 1024 },
|
||||
);
|
||||
|
||||
expect(result.wasResized).toBe(false);
|
||||
expect(result.originalWidth).toBe(2);
|
||||
expect(result.originalHeight).toBe(2);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.wasResized).toBe(false);
|
||||
expect(result!.originalWidth).toBe(2);
|
||||
expect(result!.originalHeight).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
53
packages/coding-agent/test/image-resize-callers.test.ts
Normal file
53
packages/coding-agent/test/image-resize-callers.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../src/utils/image-resize.js", () => ({
|
||||
resizeImage: vi.fn(),
|
||||
formatDimensionNote: vi.fn(() => undefined),
|
||||
}));
|
||||
|
||||
import { processFileArguments } from "../src/cli/file-processor.js";
|
||||
import { createReadTool } from "../src/core/tools/read.js";
|
||||
import { resizeImage } from "../src/utils/image-resize.js";
|
||||
|
||||
const TINY_PNG_BASE64 =
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
|
||||
|
||||
describe("image resize callers", () => {
|
||||
let testDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
testDir = join(tmpdir(), `image-resize-callers-${Date.now()}`);
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
vi.mocked(resizeImage).mockReset();
|
||||
vi.mocked(resizeImage).mockResolvedValue(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("read tool returns text-only output when auto-resize cannot produce a safe image", async () => {
|
||||
const imagePath = join(testDir, "test.png");
|
||||
writeFileSync(imagePath, Buffer.from(TINY_PNG_BASE64, "base64"));
|
||||
|
||||
const tool = createReadTool(testDir);
|
||||
const result = await tool.execute("test-read-image", { path: imagePath });
|
||||
|
||||
expect(result.content).toHaveLength(1);
|
||||
expect(result.content[0].type).toBe("text");
|
||||
expect((result.content[0] as { type: "text"; text: string }).text).toContain("Image omitted");
|
||||
});
|
||||
|
||||
it("file processor omits image attachments when auto-resize cannot produce a safe image", async () => {
|
||||
const imagePath = join(testDir, "test.png");
|
||||
writeFileSync(imagePath, Buffer.from(TINY_PNG_BASE64, "base64"));
|
||||
|
||||
const result = await processFileArguments([imagePath]);
|
||||
|
||||
expect(result.images).toHaveLength(0);
|
||||
expect(result.text).toContain("Image omitted");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user