mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 17:18:25 +00:00
fix: validate base64 image data before API submission
Adds explicit base64 format validation in sanitizeContentBlocksImages() to prevent invalid image data from being sent to the Anthropic API. The Problem: - Node's Buffer.from(str, "base64") silently ignores invalid characters - Invalid base64 passes local validation but fails at Anthropic's stricter API - Once corrupted data persists in session history, every API call fails The Fix: - Add validateAndNormalizeBase64() function that: - Strips data URL prefixes (e.g., "data:image/png;base64,...") - Validates base64 character set with regex - Checks for valid padding (0-2 '=' chars) - Validates length is proper for base64 encoding - Invalid images are replaced with descriptive text blocks - Prevents permanent session corruption Tests: - Rejects invalid base64 characters - Strips data URL prefixes correctly - Rejects invalid padding - Rejects invalid length - Handles empty data gracefully Closes #18212 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Peter Steinberger
parent
aeec95f870
commit
38c96bc53e
@@ -2,6 +2,106 @@ import sharp from "sharp";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { sanitizeContentBlocksImages, sanitizeImageBlocks } from "./tool-images.js";
|
||||
|
||||
describe("base64 validation", () => {
|
||||
it("rejects invalid base64 characters and replaces with error text", async () => {
|
||||
const blocks = [
|
||||
{
|
||||
type: "image" as const,
|
||||
data: "not-valid-base64!!!@#$%",
|
||||
mimeType: "image/png",
|
||||
},
|
||||
];
|
||||
|
||||
const out = await sanitizeContentBlocksImages(blocks, "test");
|
||||
expect(out.length).toBe(1);
|
||||
expect(out[0].type).toBe("text");
|
||||
if (out[0].type === "text") {
|
||||
expect(out[0].text).toContain("omitted image payload");
|
||||
expect(out[0].text).toContain("invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("strips data URL prefix and processes valid base64", async () => {
|
||||
// Create a small valid image
|
||||
const jpeg = await sharp({
|
||||
create: {
|
||||
width: 10,
|
||||
height: 10,
|
||||
channels: 3,
|
||||
background: { r: 255, g: 0, b: 0 },
|
||||
},
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
const base64 = jpeg.toString("base64");
|
||||
const dataUrl = `data:image/jpeg;base64,${base64}`;
|
||||
|
||||
const blocks = [
|
||||
{
|
||||
type: "image" as const,
|
||||
data: dataUrl,
|
||||
mimeType: "image/jpeg",
|
||||
},
|
||||
];
|
||||
|
||||
const out = await sanitizeContentBlocksImages(blocks, "test");
|
||||
expect(out.length).toBe(1);
|
||||
expect(out[0].type).toBe("image");
|
||||
});
|
||||
|
||||
it("rejects base64 with invalid padding", async () => {
|
||||
const blocks = [
|
||||
{
|
||||
type: "image" as const,
|
||||
data: "SGVsbG8===", // too many padding chars
|
||||
mimeType: "image/png",
|
||||
},
|
||||
];
|
||||
|
||||
const out = await sanitizeContentBlocksImages(blocks, "test");
|
||||
expect(out.length).toBe(1);
|
||||
expect(out[0].type).toBe("text");
|
||||
if (out[0].type === "text") {
|
||||
expect(out[0].text).toContain("omitted image payload");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects base64 with invalid length", async () => {
|
||||
const blocks = [
|
||||
{
|
||||
type: "image" as const,
|
||||
data: "AAAAA", // length 5 without padding is invalid (remainder 1)
|
||||
mimeType: "image/png",
|
||||
},
|
||||
];
|
||||
|
||||
const out = await sanitizeContentBlocksImages(blocks, "test");
|
||||
expect(out.length).toBe(1);
|
||||
expect(out[0].type).toBe("text");
|
||||
if (out[0].type === "text") {
|
||||
expect(out[0].text).toContain("omitted image payload");
|
||||
}
|
||||
});
|
||||
|
||||
it("handles empty base64 data gracefully", async () => {
|
||||
const blocks = [
|
||||
{
|
||||
type: "image" as const,
|
||||
data: " ",
|
||||
mimeType: "image/png",
|
||||
},
|
||||
];
|
||||
|
||||
const out = await sanitizeContentBlocksImages(blocks, "test");
|
||||
expect(out.length).toBe(1);
|
||||
expect(out[0].type).toBe("text");
|
||||
if (out[0].type === "text") {
|
||||
expect(out[0].text).toContain("omitted empty image payload");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("tool image sanitizing", () => {
|
||||
it("shrinks oversized images to <=5MB", async () => {
|
||||
const width = 2800;
|
||||
|
||||
Reference in New Issue
Block a user