mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 13:31:25 +00:00
fix(security): reject oversized base64 before decode
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildMessageWithAttachments,
|
||||
type ChatAttachment,
|
||||
@@ -44,16 +44,20 @@ describe("buildMessageWithAttachments", () => {
|
||||
});
|
||||
|
||||
it("rejects images over limit", () => {
|
||||
const big = Buffer.alloc(6_000_000, 0).toString("base64");
|
||||
const big = "A".repeat(10_000);
|
||||
const att: ChatAttachment = {
|
||||
type: "image",
|
||||
mimeType: "image/png",
|
||||
fileName: "big.png",
|
||||
content: big,
|
||||
};
|
||||
expect(() => buildMessageWithAttachments("x", [att], { maxBytes: 5_000_000 })).toThrow(
|
||||
const fromSpy = vi.spyOn(Buffer, "from");
|
||||
expect(() => buildMessageWithAttachments("x", [att], { maxBytes: 16 })).toThrow(
|
||||
/exceeds size limit/i,
|
||||
);
|
||||
const base64Calls = fromSpy.mock.calls.filter((args) => args[1] === "base64");
|
||||
expect(base64Calls).toHaveLength(0);
|
||||
fromSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -94,7 +98,8 @@ describe("parseMessageWithAttachments", () => {
|
||||
});
|
||||
|
||||
it("rejects images over limit", async () => {
|
||||
const big = Buffer.alloc(6_000_000, 0).toString("base64");
|
||||
const big = "A".repeat(10_000);
|
||||
const fromSpy = vi.spyOn(Buffer, "from");
|
||||
await expect(
|
||||
parseMessageWithAttachments(
|
||||
"x",
|
||||
@@ -106,9 +111,12 @@ describe("parseMessageWithAttachments", () => {
|
||||
content: big,
|
||||
},
|
||||
],
|
||||
{ maxBytes: 5_000_000, log: { warn: () => {} } },
|
||||
{ maxBytes: 16, log: { warn: () => {} } },
|
||||
),
|
||||
).rejects.toThrow(/exceeds size limit/i);
|
||||
const base64Calls = fromSpy.mock.calls.filter((args) => args[1] === "base64");
|
||||
expect(base64Calls).toHaveLength(0);
|
||||
fromSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("sniffs mime when missing", async () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { estimateBase64DecodedBytes } from "../media/base64.js";
|
||||
import { detectMime } from "../media/mime.js";
|
||||
|
||||
export type ChatAttachment = {
|
||||
@@ -54,6 +55,11 @@ function isImageMime(mime?: string): boolean {
|
||||
return typeof mime === "string" && mime.startsWith("image/");
|
||||
}
|
||||
|
||||
function isValidBase64(value: string): boolean {
|
||||
// Minimal validation; avoid full decode allocations for large payloads.
|
||||
return value.length > 0 && value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse attachments and extract images as structured content blocks.
|
||||
* Returns the message text and an array of image content blocks
|
||||
@@ -91,15 +97,10 @@ export async function parseMessageWithAttachments(
|
||||
if (dataUrlMatch) {
|
||||
b64 = dataUrlMatch[1];
|
||||
}
|
||||
// Basic base64 sanity: length multiple of 4 and charset check.
|
||||
if (b64.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(b64)) {
|
||||
throw new Error(`attachment ${label}: invalid base64 content`);
|
||||
}
|
||||
try {
|
||||
sizeBytes = Buffer.from(b64, "base64").byteLength;
|
||||
} catch {
|
||||
if (!isValidBase64(b64)) {
|
||||
throw new Error(`attachment ${label}: invalid base64 content`);
|
||||
}
|
||||
sizeBytes = estimateBase64DecodedBytes(b64);
|
||||
if (sizeBytes <= 0 || sizeBytes > maxBytes) {
|
||||
throw new Error(`attachment ${label}: exceeds size limit (${sizeBytes} > ${maxBytes} bytes)`);
|
||||
}
|
||||
@@ -163,15 +164,10 @@ export function buildMessageWithAttachments(
|
||||
|
||||
let sizeBytes = 0;
|
||||
const b64 = content.trim();
|
||||
// Basic base64 sanity: length multiple of 4 and charset check.
|
||||
if (b64.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(b64)) {
|
||||
throw new Error(`attachment ${label}: invalid base64 content`);
|
||||
}
|
||||
try {
|
||||
sizeBytes = Buffer.from(b64, "base64").byteLength;
|
||||
} catch {
|
||||
if (!isValidBase64(b64)) {
|
||||
throw new Error(`attachment ${label}: invalid base64 content`);
|
||||
}
|
||||
sizeBytes = estimateBase64DecodedBytes(b64);
|
||||
if (sizeBytes <= 0 || sizeBytes > maxBytes) {
|
||||
throw new Error(`attachment ${label}: exceeds size limit (${sizeBytes} > ${maxBytes} bytes)`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user