fix(screenshot): reject invalid PNG header dimensions (#2472)

This commit is contained in:
Rudy Mizrahi Celekli 2026-10-13 02:50:44 -04:00 committed by GitHub
parent 68ecf3df8c
commit e74e4e951b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 90 additions and 1 deletions

View File

@ -33,3 +33,24 @@ it("rejects incomplete headers, invalid signatures and zero dimensions", () => {
expect(readPngHeaderDimensions(zero)).toBeUndefined();
}
});
it.each([0, 12, 14, 0xffff_ffff])("rejects IHDR data length %s", (length) => {
const bytes = Buffer.from(png);
bytes.writeUInt32BE(length, 8);
expect(readPngHeaderDimensions(bytes)).toBeUndefined();
});
it.each([16, 20])("rejects out-of-range PNG dimension at %s", (offset) => {
const bytes = Buffer.from(png);
bytes.writeUInt32BE(0x8000_0000, offset);
expect(readPngHeaderDimensions(bytes)).toBeUndefined();
});
it("accepts the largest dimension representable by the PNG specification", () => {
const bytes = Buffer.from(png);
bytes.writeUInt32BE(0x7fff_ffff, 16);
expect(readPngHeaderDimensions(bytes)).toEqual({
width: 0x7fff_ffff,
height: 1,
});
});

View File

@ -8,10 +8,16 @@ export const readPngHeaderDimensions = (
if (
bytes.byteLength < 24 ||
!bytes.subarray(0, 8).equals(PNG_SIGNATURE) ||
bytes.readUInt32BE(8) !== 13 ||
!bytes.subarray(12, 16).equals(IHDR_TYPE)
)
return undefined;
const width = bytes.readUInt32BE(16);
const height = bytes.readUInt32BE(20);
return width === 0 || height === 0 ? undefined : { width, height };
return width === 0 ||
height === 0 ||
width > 0x7fff_ffff ||
height > 0x7fff_ffff
? undefined
: { width, height };
};

View File

@ -35,6 +35,37 @@ const validPng = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jv4sAAAAASUVORK5CYII=",
"base64",
);
it.each([
{ offset: 8, value: 12 },
{ offset: 16, value: 0x8000_0000 },
{ offset: 20, value: 0x8000_0000 },
])(
"rejects invalid IHDR metadata with a matching digest",
async ({ offset, value }) => {
const bytes = Buffer.from(validPng);
bytes.writeUInt32BE(value, offset);
const screenshot = {
mime_type: "image/png",
base64: bytes.toString("base64"),
sha256: createHash("sha256").update(bytes).digest("hex"),
width: bytes.readUInt32BE(16),
height: bytes.readUInt32BE(20),
};
const result = await observeNativeUi(
target,
"observe_native_ui",
{ ...scope, screenshot: true },
{
environment: {},
invoke: async () => ({
ok: true,
result: { ...snapshot, screenshot },
}),
},
);
expect(result.ok).toBe(false);
},
);
describe("native UI screenshot validation", () => {
it("rejects malformed or mismatched screenshot bytes from the helper", async () => {
const screenshot = {

View File

@ -3,6 +3,37 @@ import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { captureWebScreenshotInputSchema } from "../../../src/domain/webScreenshot.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
describe("screenshot committed document identity", () => {
it.each([
{ offset: 8, value: 14 },
{ offset: 16, value: 0x8000_0000 },
{ offset: 20, value: 0x8000_0000 },
])("rejects invalid screenshot IHDR metadata", async ({ offset, value }) => {
const bytes = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jv4sAAAAASUVORK5CYII=",
"base64",
);
bytes.writeUInt32BE(value, offset);
const browser = await startFakeCdpBrowser({
commandResult: ({ method }) =>
method === "Page.captureScreenshot"
? { data: bytes.toString("base64") }
: undefined,
});
onTestFinished(async () => {
await browser.close();
});
const result = await new CdpBrowserProvider().captureScreenshot(
captureWebScreenshotInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
}),
);
expect(result).toMatchObject({
ok: false,
error: { reason: "protocol_error" },
});
});
it.each(["loader-main", "loader-reloaded"])(
"distinguishes %s for the same URL",
async (screenshotDocumentLoader) => {